JVM垃圾回收器与调优参数
引言
JVM为了更有效率的对堆空间进行垃圾回收,把堆空间进行了分代,分为年轻代、老年代和永久代(在1.8版本以后,永久代已经被彻底移除了,被元空间取而代之)。
当一个对象出生时,会首先选择在eden区为对象分配空间,当eden区被占满分配不下时,会触发一次Minor GC,MinorGC运行时,会将垃圾回收后eden区存活的对象移至Survivor1区,当Survivor某个区有了对象后,下一次进行MinorGC时,垃圾回收后会将eden区和存放对象的Survivor区里的剩余存活对象复制到另外一块空白的Survivor区,这就是年轻代垃圾回收的触发条件和回收过程。
对于老年代而言,老年代里的对象存活率相对较高,如果依然采用年轻代里的复制算法,耗时会相对较长,所以对老年代采用的时标记——清除或整理算法。CMS老年代回收器用的是标记清除,容易产生碎片,而PS+PO用的是标记整理,耗时相对于CMS稍微较长。可以这么说,在G1没有出来之前,如果追求的是低延迟,则使用CMS垃圾回收器;若追求的是高吞吐量,则可以选择使用PS + PO垃圾回收器。
基本概念
吞吐量(Throughput)
吞吐量(TPS)是指系统在单位时间内处理请求的数量。对于无并发的应用系统而言,吞吐量与响应时间成严格的反比关系,实际上此时吞吐量就是响应时间的倒数。前面已经说过,对于单用户的系统,响应时间(或者系统响应时间和应用延迟时间)可以很好地度量系统的性能,但对于并发系统,通常需要用吞吐量作为性能指标。
响应时间(Response Time)
响应时间是指系统对请求作出响应的时间。直观上看,这个指标与人对软件性能的主观感受是非常一致的,因为它完整地记录了整个计算机系统处理请求的时间。由于一个系统通常会提供许多功能,而不同功能的处理逻辑也千差万别,因而不同功能的响应时间也不尽相同,甚至同一功能在不同输入数据的情况下响应时间也不相同。所以,在讨论一个系统的响应时间时,人们通常是指该系统所有功能的平均时间或者所有功能的最大响应时间。当然,往往也需要对每个或每组功能讨论其平均响应时间和最大响应时间。
QPS每秒查询率(Query Per Second)
每秒查询率QPS是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准,在因特网上,作为域名系统服务器的机器的性能经常用每秒查询率来衡量。对应fetches/sec,即每秒的响应请求数,也即是最大吞吐能力。
JVM性能监控
JVM调优参数
JVM调优参数
参数分类
1、标准参数(-),所有的JVM实现都必须实现这些参数的功能,而且向后兼容。例如:-verbose:class(输出jvm载入类的相关信息,当jvm报告说找不到类或者类冲突时可此进行诊断);-verbose:gc(输出每次GC的相关情况);-verbose:jni(输出native方法调用的相关情况,一般用于诊断jni调用错误信息)。
2、非标准参数(-X),默认jvm实现这些参数的功能,但是并不保证所有jvm实现都满足,且不保证向后兼容。例如:-Xms512m;-Xmx512m;-Xmn200m;-Xss128k;-Xloggc:file(与-verbose:gc功能类似,只是将每次GC事件的相关情况记录到一个文件中,文件的位置最好在本地,以避免网络的潜在问题。若与verbose命令同时出现在命令行中,则以-Xloggc为准)。
3、非Stable参数(-XX),此类参数各个jvm实现会有所不同,将来可能会随时取消,需要慎重使用。例如:-XX:PermSize=64m;-XX:MaxPermSize=512m。
查看参数
#查看命令行带的参数以及少量默认参数
java -XX:+PrintCommandLineFlags -version
#查看最终使用的参数(以列的形式列出,自行grep)
java -XX:+PrintFlagsFinal -XX:+UseG1GC -version | grep GC
#运行时打印输出GCDetails
-XX:+PrintGCDetails
#验证虚拟机参数是否正确
-XX:+PrintVMOptions
内存管理参数
参数 | 功能 | 默认值 | 描述 |
---|---|---|---|
-Xms | 初始Heap值 | 物理内存的1/64但小于1G | Server端JVM最好将-Xms和-Xmx设为相同值,避免每次垃圾回收完成后JVM重新分配内存 |
-Xmx | 可用最大Heap值 | 物理内存的1/4但小于1G | 默认当空余堆内存小于40%时,JVM会增大Heap到-Xmx指定的大小,可通过-XX:MinHeapFreeRation=来指定这个比列。最佳设值应该视物理内存大小及计算机内其他内存开销而定。 |
-Xmn | 年轻代大小 | 整堆的1/3 | 整个堆大小=年轻代大小 + 年老代大小 + 持久代大小,Sun官方推荐配置为整个堆的3/8 |
-Xss | 线程的Stack大小 | 1024kb | JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。可以适当减少增加thread可使用数量 |
-XX:SurvivorRatio | eden区和1个survivor区的比值 | 8 | 设置为8,则两个Survivor区与一个Eden区的比值为2:8,一个Survivor区占整个年轻代的1/10 |
-XX:NewRatio | 老年代(除去永久代)与年轻代所占堆的比值 2/3:1/3 | 2 | 年轻代与年老代所占比值为1:4 ,使用CMS收集器无效 |
-XX:PermSize | 永久代(perm gen)初始值 | 物理内存的1/64 | 设置永久代(perm gen)初始值 |
-XX:MaxPermSize | 设置持久代最大值 | 物理内存的1/4 | 设置永久代(perm gen)最大值 |
-XX:+UseFastAccessorMethods | 内联优化 | false | 指内联函数的优化,何为内联函数呢,即一个方法里面调用了另外一个方法,JVM在编译的时候把被调用的方法合入到调用的方法里面,这样就能减少栈帧的创建(因为每一个方法执行时都会创建一个栈帧),节约内存 |
-XX:+DisableExplicitGC | 关闭System.gc()显示调用 | false | |
-XX:MaxTenuringThreshold | 垃圾最大年龄 | CMS:6 G1:15 Object有个标记位 | 对象存活的年龄,到达某个值后,就会晋升到老年代,而这个值最大为就是这个参数,年龄多大晋升到老年代,是GC器动态调节的,但是最大不会超过这个设定值 |
-XX:UseTlab | 使用线程本地分配缓存区 | true | TLAB只是让每个线程有私有的分配指针,但底下存对象的内存空间还是给所有线程访问的,只是其它线程无法在这个区域分配而已。 |
-XX:+TLABSize | tlab的初始大小, 默认0 | 0 | |
-XX:TLABWasteTargetPercent | TLAB占eden区的百分比 | 1% | |
-XX:TLABRefillWasteFraction | 设置进入TLAB空间,单个对象大小 | 64 | 如果,对象小于整个空间的1/64,则放在TLAB区;如果,对象大于整个空间的1/64,则放在堆区 |
-XX:+CollectGen0First | FullGC时是否先YGC | false | FullGC时是否先YGC |
-XX:+ScavengeBeforeFullGC | Full GC 前执行一次 Minor GC | true | 在执行 Full GC 前执行一次 Minor GC可以较少老年代中对象“意外”存活的现象。 |
垃圾回收器参数
并行收集器相关参数
参数 | 功能 | 默认值 | 描述 |
---|---|---|---|
-XX:+UseParallelGC | Full GC采用parallel MSC | 选择垃圾收集器为并行收集器.此配置仅对年轻代有效.即上述配置下,年轻代使用并发收集,而年老代仍旧使用串行收集.(此项待验证) | |
XX:+UseParNewGC | 设置年轻代为并行收集 | 可与CMS收集同时使用JDK5.0以上,JVM会根据系统配置自行设置,所以无需再设置此值 | |
XX:+UseParallelOldGC | 年老代垃圾收集方式为并行收集(Parallel Compacting) | 这个是JAVA 6出现的参数选项 | |
-XX:ParallelGCThreads | 并行收集器的线程数 | 此值最好配置与处理器数目相等 同样适用于CMS | |
-XX:MaxGCPauseMillis | 每次年轻代垃圾回收的最长时间(最大暂停时间) | 如果无法满足此时间,JVM会自动调整年轻代大小,以满足此值. | |
-XX:+UseAdaptiveSizePolicy | 自动选择年轻代区大小和相应的Survivor区比例 | 设置此选项后,并行收集器会自动选择年轻代区大小和相应的Survivor区比例,以达到目标系统规定的最低相应时间或者收集频率等,此值建议使用并行收集器时,一直打开. | |
-XX:+ScavengeBeforeFullGC | Full GC前调用YGC | true | Do young generation GC prior to a full GC. (Introduced in 1.4.1.) |
CMS相关参数
参数 | 功能 | 默认值 | 描述 |
---|---|---|---|
-XX:+UseConcMarkSweepGC | 使用CMS内存收集 | 测试中配置这个以后,-XX:NewRatio=4的配置失效了,原因不明.所以,此时年轻代大小最好用-Xmn设置. | |
-XX:ParallelCMSThreads | 设置CMS线程数量 | CPU核数一半 | 设置CMS线程数量,一般为CPU核数的一半,CMS线程是在老年代,要留一些CPU给用户线程 |
-XX:+AggressiveHeap | 优化大内存 | true | |
-XX+UseCMSCompactAtFullCollection | 在FULL GC的时候, 对年老代的压缩 | true | CMS是不会移动内存的, 因此, 这个非常容易产生碎片, 导致内存不够用, 因此, 内存的压缩这个时候就会被启用。 增加这个参数是个好习惯。可能会影响性能,但是可以消除碎片 |
-XX:CMSFullGCsBeforeCompaction | 多少次后进行内存压缩 | 0 | 由于并发收集器不对内存空间进行压缩,整理,所以运行一段时间以后会产生"碎片",使得运行效率降低.此值设置运行多少次GC以后对内存空间进行压缩,整理. |
-XX:+UseCMSInitiatingOccupancyOnly | 使用手动定义初始化定义开始CMS收集 | 禁止hostspot自行触发CMS GC | |
-XX:CMSInitiatingOccupancyFraction=70 | 使用cms作为垃圾回收使用70%后开始CMS收集 | 92 | |
-XX:CMSInitiatingPermOccupancyFraction | 设置Perm Gen使用到达多少比率时触发 | 92 | 设置Perm Gen使用到达多少比率时触发 |
-XX:+CMSClassUnloadingEnabled | 是否对永久代进行垃圾回收,类型卸载 | true | 是否对永久代进行垃圾回收,类型卸载 |
-XX:+CMSParallelRemarkEnabled | 开启并行标记 | true | 使用 CMS 作为GC方式,且开启并行标记,以减少服务端延迟时间 |
-XX:+CMSScavengeBeforeRemark | CMS Remark阶段前,执行一次 Minor GC | false | 在执行CMS Remark阶段前,执行一次 Minor GC,以降低STW的时间。通过 Minor GC 可以减少新生代对老年代对象的引用,这样可以减少根对象(GC Roots)数量,从而降低 CMS Remark 的工作量 |
PromotionFailed
垃圾回收时promotionfailed是个很头痛的问题,一般可能是两种原因产生,第一个原因是救助空间不够,救助空间里的对象还不应该被移动到年老代,但年轻代又有很多对象需要放入救助空间;第二个原因是年老代没有足够的空间接纳来自年轻代的对象;这两种情况都会转向Full GC,网站停顿时间较长。
解决方案一:
第一个原因最终解决办法是去掉救助空间,设置-XX:SurvivorRatio=65536 -XX:MaxTenuringThreshold=0即可,第二个原因解决办法是设置CMSInitiatingOccupancyFraction为某个值(假设70),这样年老代空间到70%时就开始执行CMS,年老代有足够的空间接纳来自年轻代的对象。
解决方案一的改进方案:
又有改进了,上面方法不太好,因为没有用到救助空间,所以年老代容易满,CMS执行会比较频繁。改善了一下,还是用救助空间,但是把救助空间加大,这样也不会有promotion failed。具体操作上,32位Linux和64位Linux好像不一样,64位系统似乎只要配置MaxTenuringThreshold参数,CMS还是有暂停。为了解决暂停问题和promotion failed问题,最后设置-XX:SurvivorRatio=1 ,并把MaxTenuringThreshold去掉,这样即没有暂停又不会有promotoin failed,而且更重要的是,年老代和永久代上升非常慢(因为好多对象到不了年老代就被回收了),所以CMS执行频率非常低,好几个小时才执行一次,这样,服务器都不用重启了。
-Xmx4000M -Xms4000M -Xmn600M-XX:PermSize=500M -XX:MaxPermSize=500M -Xss256K -XX:+DisableExplicitGC-XX:SurvivorRatio=1 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC-XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=0-XX:+CMSClassUnloadingEnabled -XX:LargePageSizeInBytes=128M-XX:+UseFastAccessorMethods -XX:+UseCMSInitiatingOccupancyOnly-XX:CMSInitiatingOccupancyFraction=80 -XX:SoftRefLRUPolicyMSPerMB=0-XX:+PrintClassHistogram -XX:+PrintGCDetails -XX:+PrintGCTimeStamps-XX:+PrintHeapAtGC -Xloggc:log/gc.log
G1常用参数
参数 | 功能 | 默认值 | 描述 |
---|---|---|---|
-XX:+UseG1GC | 开启G1垃圾回收 | ||
-XX:MaxGCPauseMillis | 暂停时间 | 200ms | G1会尝试调整Young区的块数来达到这个值 |
-XX:+G1HeapRegionSize | 分区大小 | 建议逐渐增大该值,1 2 4 8 16 32。 随着size增加,垃圾的存活时间更长,GC间隔更长,配置了XX:G1HeapRegionSize,那么以配置为准,默认情况下,region_size = 平均堆大小((最大堆+最小堆)/2)除以2048个分区,最终取值1 2 4 8 16 32 最接近的一个,MIN_REGION_SIZE<=HeapRegionSize<=MAX_REGION_SIZE | |
-XX:G1NewSizePercent | 新生代比例下限 | 5% | |
-XX:G1MaxNewSizePercent | 新生代比例上限 | 60% | |
-XX:ConcGCThreads | 并发GC线程数 | 默认是-XX:ParallelGCThreads/4 | 在非STW期间的GC工作线程数,当然其他的线程很多工作在应用上。当并发周期时间过长时,可以尝试调大GC工作线程数,但是这也意味着此期间应用所占的线程数减少,会对吞吐量有一定影响。ConcGCThreads = (3 + ParallelGCThreads) / 4 |
-XX:ParallelGCThreads | STW阶段工作的GC线程数 | ||
-XX:InitiatingHeapOccupancyPercent | 触发全局并发标记的老年代使用占比 | 45% | |
-XX:G1HeapWastePercent | 触发Mixed GC的堆垃圾占比 | 默认值5% | 在全局标记结束后能够统计出所有Cset内可被回收的垃圾占整对的比例值,如果超过5%,那么就会触发之后的多轮Mixed GC |
-XX:G1ReservePercent | G1为分配担保预留的空间比例 | 默认10% | 老年代会预留10%的空间来给新生代的对象晋升,如果经常发生新生代晋升失败而导致Full GC,那么可以适当调高此阈值。但是调高此值同时也意味着降低了老年代的实际可用空间。 |
G1调优建议 |
- 不要手动设置新生代和老年代的大小,只设置这个堆的大小
- 不断调优暂停时间目标-XX:MaxGCPauseMillis
- 适当增加堆内存大小
G1GC
G1 GC是启发式算法,会动态调整年轻代的空间大小。目标也就是为了达到接近预期的暂停时间。G1提供了两种GC模式,Young GC和Mixed GC,两种都是Stop The World(STW)的。
- Young GC
Young GC主要是对Eden区进行GC,它在Eden空间耗尽时会被触发。在这种情况下,Eden空间的数据移动到Survivor空间中,如果Survivor空间不够,Eden空间的部分数据会直接晋升到老年代空间。Survivor区的数据移动到新的Survivor区中,也有部分数据晋升到老年代空间中。最终Eden空间的数据为空,GC停止工作,应用线程继续执行。 - Mixed GC
Mix GC不仅进行正常的新生代垃圾收集,同时也回收部分后台扫描线程标记的老年代分区。GC步骤分2步: 全局并发标记(global concurrent marking)和 拷贝存活对象(evacuation)。
在进行Mix GC之前,会先进行global concurrent marking(全局并发标记)。 global concurrent marking的执行过程是怎样的呢?在G1 GC中,它主要是为Mixed GC提供标记服务的,并不是一次GC过程的一个必须环节。
Mixed GC的触发也是由一些参数控制。比如XX:InitiatingHeapOccupancyPercent表示老年代占整个堆大小的百分比,默认值是45%,达到该阈值就会触发一次Mixed GC。
G1的垃圾回收过程是和应用程序并发执行的,当Mixed GC的速度赶不上应用程序申请内存的速度的时候,Mixed G1就会降级到Full GC,使用的是Serial GC。Full GC会导致长时间的STW,应该要尽量避免。