JVM调优之参数优化

一、先看一段JVM参数配置:

  • -XX:+UseCompressedOops
    -Xms3096m
    -Xmx3096m
    -XX:PermSize=512
    -XX:MaxPermSize=1024m
    -XX:NewSize=2048m
    -XX:MaxNewSize=2048m
    -XX:SurvivorRatio=8

    -verbose:gc
    -Xloggc:/data/dataLogs/gc/gc.log
    -XX:+PrintGCDateStamps
    -XX:+PrintGCDetails

    -XX:+UseConcMarkSweepGC
    -XX:CMSInitiatingOccupancyFraction=70
    -XX:+UseCMSCompactAtFullCollection
    -XX:+CMSClassUnloadingEnabled
    -XX:+DisableExplicitGC

    -XX:+HeapDumpOnOutOfMemoryError
    -XX:HeapDumpPath=/data/dataLogs/dump

参数介绍:
-XX:+UseCompressedOops 这个可以压缩指针,起到节约内存占用的新参数。
通常64位JVM消耗的内存会比32位的大1.5倍,这是因为对象指针在64位架构下,长度会翻倍(更宽的寻址)。
对于那些将要从32位平台移植到64位的应用来说,平白无辜多了1/2的内存占用,从JDK1.6开始支持。
-Xms 设置JVM初始堆内存。
-Xmx 设置JVM最大堆内存。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。
-Xmn:设置年轻代大小。
-Xss:设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为128k.更具应用的线程所需内存大小进行 调整.在相同物理内存下,减小这个值能生成更多的线程.但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。
-XX:MaxTenuringThreshold:设置垃圾最大年龄.如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代. 对于年老代比较多的应用,可以提高效率.如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活 时间,增加在年轻代即被回收的概率。

-XX:MaxPermSize 设置持久代初始值。
-XX:MaxPermSize 设置持久代最大值。
-XX:NewSize 设置年轻代初始值。
-XX:MaxNewSize 设置年轻代最大值。
XX:NewRatio:设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代).设置为2,则年轻代与年老代所占比值为1:2,年轻代占整个堆栈的1/3。
-XX:SurvivorRatio:设置年轻代中Eden区与Survivor区的大小比值.设置为8,则两个Survivor区与一个Eden区的比值为2:8,一个Survivor区占整个年轻代的1/10。

-verbose:gc 表示输出虚拟机中GC的详细情况。
-Xloggc 设置gc日志路径。
-XX:+PrintGCDateStamps 输出GC的时间戳(以日期的形式,如 2013-05-04T21:53:59.234+0800)。
-XX:+PrintGCDetails 输出GC的详细日志。

-XX:+UseConcMarkSweepGC CMS收集,设置年老代为并发收集。
-XX:+UseCMSCompactAtFullCollection 打开内存空间的压缩和整理,在Full GC后执行。可能会影响性能,但可以消除内存碎片。
-XX:CMSInitiatingOccupancyFraction 是指设定CMS在对内存占用率达到70%的时候开始GC(因为CMS会有浮动垃圾,所以一般都较早启动GC)。
-XX:+CMSClassUnloadingEnabled 默认情况下,CMS不会处理永久代中的垃圾,可以通过开启CMSPermGenSweepingEnabled配置来开启永久代中的垃圾回收,开启后会有一组后台线程针对永久代做收集,需要注意的是,触发永久代进行垃圾收集的指标跟触发老年代进行垃圾收集的指标是独立的,老年代的阈值可以通过CMSInitiatingPermOccupancyFraction参数设置,这个参数的默认值是80%。开启对永久代的垃圾收集只是其中的一步,还需要开启另一个参数——CMSClassUnloadingEnabled,使得在垃圾收集的时候可以卸载不用的类。
-XX:+DisableExplicitGC,这个参数作用是禁止代码中显示调用GC。代码如何显示调用GC呢,通过System.gc()函数调用。如果加上了这个JVM启动参数,那么代码中调用System.gc()没有任何效果。
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath 当发生OutOfMemoryError错误时,才能触发-XX:HeapDumpOnOutOfMemoryError 输出到-XX:HeapDumpPath指定位置。

二、垃圾回收算法:
1、标记-清除算法(Mark-Sweep)
标记-清除 算法是最基础的算法,分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。它主要由两个缺点:一个是效率问题,标记和清除过程的效率都不高;另一个是空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致当程序在以后的运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。具体过程如下图所示:
在这里插入图片描述
2、复制算法(Copying)
针对新生代。为了解决标记清除算法的效率问题,出现了复制算法,它将可用内存按容量划分为大小相等的两块,每次使用其中的一块。当这块的内存用完了,就将还存活着的对象复制到另一块上面,然后再把已使用过的内存空间一次清理掉。优点是每次都是对其中的一块进行内存回收,内存分配时就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。缺点是将内存缩小为原来的一半,代价太高了一点。

     现在的商业虚拟机都采用复制收集算法来回收新生代,有研究表明,新生代中的对象98%是朝生夕死的,所以并不需要按照1:1的比例来划分内存空间,而是将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中的一块Survivor。当回收时,将Eden和Survivor中还存活着的对象一次性地拷贝到另外一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间。HotSpot虚拟机默认Eden和Survivor的大小比例是8:1,也就是每次新生代中可用内存空间为整个新生代容量的90%(80%+10%),只有10%的内存是会被“浪费”的。当然,并不能保证每次回收都只有10%的对象存活,当Survivor空间不够用时,需要依赖其他内存(这里指老年代)进行分配担保(Handle Promotion)。即如果另外一块Survivor空间没有足够的空间存放上一次新生代收集下来的存活对象,这些对象将直接通过分配担保机制进入老年代。

具体过程如下图所示:
在这里插入图片描述
3、标记-整理算法(Mark-Compact)
针对老年代。为了解决Copying算法的缺陷,充分利用内存空间,提出了Mark-Compact算法。该算法标记阶段和Mark-Sweep一样,但是在完成标记之后,它不是直接清理可回收对象,而是将存活对象都向一端移动,然后清理掉端边界以外的内存。具体过程如下图所示:
在这里插入图片描述
4、分代收集算法(Generational Collection)
代收集算法是目前大部分JVM的垃圾收集器采用的算法。它的核心思想是根据对象存活的生命周期将内存划分为若干个不同的区域。一般情况下将堆区划分为老年代(Tenured Generation)和新生代(Young Generation),老年代的特点是每次垃圾收集时只有少量对象需要被回收,而新生代的特点是每次垃圾回收时都有大量的对象需要被回收,那么就可以根据不同代的特点采取最适合的收集算法。

	前大部分垃圾收集器对于新生代都采取Copying算法,因为新生代中每次垃圾回收都要回收大部分对象,也就是说需要复制的操作次数较少,但是实际中并不是按照1:1的比例来划分新生代的空间的,一般来说是将新生代划分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden空间和其中的一块Survivor空间,当进行回收时,将Eden和Survivor中还存活的对象复制到另一块Survivor空间中,然后清理掉Eden和刚才使用过的Survivor空间。 而由于老年代的特点是每次回收都只回收少量对象,一般使用的是Mark-Compact算法。

三、垃圾回收器:
如果说收集算法是内存回收的方法论,垃圾收集器就是内存回收的具体实现。下图展示了7种不同分代的收集器,如果两个收集器之间存在连线,就说明他们可以搭配使用。并没有最好的收集器这一说,我们需要选择的是对具体应用最合适的收集器。
在这里插入图片描述

1、Serial收集器(用于新生代)
在这里插入图片描述

	单线程,在进行垃圾收集时必须暂停其他所有的工作线程("Stop the World")。虚拟机运行在Client模式下的默认新生代收集器。简单而高效(与其他收集器的单线程比),对于限定单个CPU的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程效率。

2、ParNew收集器(新生代)
在这里插入图片描述

 ParNew收集器其实是Serial收集器的多线程版本,它是许多运行在Server模式下的虚拟机中首选的新生代收集器,因为除了Serial收集器外,目前只有它能与CMS收集器配合工作。

3、Parallel Scavenge收集器

   “吞吐量优先”收集器,适用于新生代。 使用复制算法,并行多线程,这些特点与ParNew一样,它的独特之处是它的关注点与其他收集器不同,CMS等收集器的关注点是尽可能缩短垃圾收集时用户线程的停顿时间,而Parallel Scavenge收集器的目的则是达到一个可控制的吞吐量(Throughput),即CPU用于运行用户代码的时间与CPU总消耗时间的比值,吞吐量=运行用户代码时间 /(运行用户代码时间+垃圾收集时间),虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,吞吐量就是99%。
   停顿时间越短对于需要与用户交互的程序来说越好,良好的响应速度能提升用户的体验;
   高吞吐量可以最高效率地利用CPU时间,尽快地完成程序的运算任务,主要适合在后台运算而不太需要太多交互的任务。

参数设置:
-XX:MaxGCPauseMillis 控制最大垃圾收集停顿时间。(大于0的毫秒数)停顿时间缩短是以牺牲吞吐量和新生代空间换取的。(新生代调的小,吞吐量跟着小,垃圾收集时间就短,停顿就小)。
-XX:GCTimeRatio 直接设置吞吐量大小,0<x<100 的整数,允许的最大GC时间=1/(1+x)。
-XX:+UseAdaptiveSizePolicy  一个开关参数,开启GC自适应调节策略(GC Ergonomics),将内存管理的调优任务(新生代大小-Xmn、Eden与Survivor区的比例-XX:SurvivorRatio、晋升老年代对象年龄-XX: PretenureSizeThreshold 、等细节参数)交给虚拟机完成。这是Parallel Scavenge收集器与ParNew收集器的一个重要区别,另一个是吞吐量。

4、Serial Old收集器

	标记-整理算法。适用于老年代,它是Serial收集器的老年代版本,单线程,使用“标记-整理”算法。主要意义是被Client模式下的虚拟机使用。如果在Server模式下,它还有两大用途:在JDK1.5及之前的版本中与Parallel Scavenge收集器搭配使用;作为CMS 收集器的后备预案,在并发收集发生Concurrent Mode Failure的时候使用。运行过程同Serial收集器。

5、Parallel Old收集器
在这里插入图片描述

	标记-整理算法。适用于老年代,它是Parallel Scavenge收集器的老年代版本,多线程,使用“标记-整理”算法。在注重吞吐量及CPU资源敏感的场合,都可以优先考虑Parallel Scavenge+Parallel Old收集器。
**6、CMS收集器(Concurrent Mark Sweep)**

在这里插入图片描述

“标记-清除”算法。它是一种以获取最短回收停顿时间为目标的收集器。优点:并发收集,低停顿。目前很大一部分Java应用都集中在互联网站或B/S系统的服务端上,这类应用尤其重视服务的响应速度,希望系统停顿时间最短,以给用户带来较好的体验,CMS收集器就非常符合这类应用的需求。运作过程较复杂,分为4个步骤:
初始标记(CMS initial mark):需要“Stop The World”,标记GC Roots能直接关联到的对象,速度快。
并发标记(CMS concurrent mark):进行GC Roots Tracing 过程
重新标记(CMS remark):需要“Stop The World”,修正并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录。停顿时间:初始标记<重新标记<<并发标记
并发清除(CMS concurrent sweep):时间较长。

缺点:
对CPU资源非常敏感,面向并发设计的程序都会对CPU资源较敏感。CMS默认的回收线程数: (CPU数量+3)/4
无法处理浮动垃圾,可能出现“Concurrent Mode Failure”失败而导致另一次Full GC的产生。并发清理阶段用户程序运行产生的垃圾过了标记阶段所以无法在本次收集中清理掉,称为浮动垃圾。CMS收集器默认在老年代使用了68%的空间后被激活。若老年代增长的不是很快,可以适当调高参数-XX:CMSInitiatingOccupancyFraction  提高触发百分比,但调得太高会容易导致“Concurrent Mode Failure”失败。
基于“标记-清除”算法会产生大量空间碎片。提供开关参数-XX:+UseCMSCompactAtFullCollection 用于在“ 享受”完Full GC服务之后进行碎片整理过程,内存整理的过程是无法并发的。但是停顿时间会变长。

7、G1收集器(Garbage First)

它是当前收集器技术发展的最前沿成果。与CMS相比有两个显著改进:
基于“标记-整理”算法实现收集器
非常精确地控制停顿

G1收集器可以在几乎不牺牲吞吐量的前提下完成低停顿的内存回收,这是由于它能够极力避免全区域的垃圾收集,之前的收集器进行收集的范围都是整个新生代或老年代,而G1将整个Java堆(包括新生代、老年代)划分为多个大小固定的独立区域(Region),并且跟踪这些区域里面的垃圾堆积程度,在后台维护一个优先列表,每次根据允许的收集时间,优先回收垃圾最多的区域(这就是Garbage First名称的由来)。区域划分、有优先级的区域回收,保证了G1收集器在有限的时间内可以获得最高的收集效率。

三、gc日志:
在这里插入图片描述
在这里插入图片描述

作者简介:就职于甜橙金融信息技术部,负责服务端开发,专注于微服务、分布式、性能调优、高可用,欢迎各位同仁沟通交流。

发布了4 篇原创文章 · 获赞 17 · 访问量 2573

猜你喜欢

转载自blog.csdn.net/weixin_39178876/article/details/93899079