大家好,我是北军。最近,该项目一直在运行性能测试。看到程序的MinorGC频率变高了,每分钟的GC时间变长了,心里总忐忑不安,过一会又不会503了。前言对于Java程序员来说,GC永远是一个绕不过去的知识点,总会有用到这些内容的时刻。只是用的时候书少,需要优化GC的时候,看GC的每一步细节,很难快速完成任务。本文内容只是干货满满,可以说是立马拿来用。现在我们废话少说,走吧。一、GC算法的种类目前,OpenJDK中有几种常见的GC算法。SerialGCParallelGCCMSGC(ConcurrentMark&Sweep)G1GCZGC目前大多数人使用Java8.如果没有明确指定GC算法,Java8将使用默认的ParallelGC。从Java9开始,默认的GC是G1GC算法。Java17默认也是G1GC,个别版本会略有不同。以下是常见的GC算法使用命令。GCAlgorithmJVMargumentSerialGC-XX:+UseSerialGCParallelGC-XX:+UseParallelGCCMSGC-XX:+UseConcMarkSweepGCG1GC-XX:+UseG1GCZGC-XX:+UseZGC网上大部分人都称赞ZGC算法的性能,如果你使用Java11或者以上版本,再考虑使用ZGC。但是,包括笔者在内的大部分同学已经长期在Java8中无法自拔,所以本文将避开ZGC。2.JVM的一些重要参数JVM中的参数分为3类:标准参数(-),所有的JVM都必须实现这些参数的功能,并且必须向后兼容。java-version(-X)等非标准参数,默认JVM实现了这些功能,不保证所有JVM都能使用,不向下兼容。Non-Stableparameters(-XX),这些参数对于每个JVM实现都会有所不同,以后可能会取消,需要谨慎使用关于非标准参数,我们可以使用java-X命令来查找这些参数,-Xmn新生代内存大小,包括E区和两个S区,使用方法如下:-Xmn65535、-Xmn2048k、-Xmn512m、-Xmn2g(-Xms、-Xmx也写法相同)-XmsInitialheapsize,堆大小的最小值,默认值为物理内存的1/64(小于1G),默认情况下,如果堆中可用内存小于40%(调整参数-X:MinHeapFreeRatio=40)、堆内存会开始增加,直到-Xmx的大小-Xmx堆的最大值,默认是物理内存的1/64。如果不设置Xms和Xmx,则两者大小相同。默认情况下,当堆中可用内存大于70%时(调整参数-X:MaxHeapFreeRatio=70),堆内存会开始减少,直到达到-Xms大小-Xss线程栈内存,默认为1m,如果项目使用过多的lombok,编译时可能会出现stackoverflow,需要多配置一点stackmemory。-XX:MaxTeurningThreashold新生代存活对象晋升到老年代的年龄阈值。对象头使用4位来存储age,所以最大值为15。默认值为15。如果年轻代垃圾回收后一段时间内内存使用率保持在一定的高水平,之后会恢复正常一段时间。那么可以适当降低年龄阈值,让存活的对象更早进入老年代,提高年轻代的可用性。3.哪个GC最合适?由于大部分同学使用的是Java8,所以肯定会在ParallelGC和G1GC之间做出选择。关于哪种GC最合适,下面我们分别来看一下。3.1如果选择Para??llelGCParallelGC是Java8默认的GC算法。对于新生代,它使用ParallelScavenge(复制算法),而老年代的垃圾回收则不同。有两种组合:使用-XX:UseParallelGC参数,新生代使用ParallelScavenge垃圾回收算法,老年代使用PSMarkSweep(SerialOld)垃圾回收算法(mark-sort算法)。使用-XX:UseParallelOldGC参数,新生代使用ParallelScavenge垃圾回收算法,老年代使用ParallelOld垃圾回收算法(标记排序算法)。3.1.1ParallelScavengeParallelScavenge是新一代的并行收集器,使用了复制算法。主要关注的是吞吐量,它是JVM运行时花费的非垃圾收集时间的百分比。ParallelScavenge收集器有两个控制吞吐量的重要参数:最大停顿时间-XX:MaxGCPauseMills=100其值大于0毫秒,垃圾收集器会尽量保证回收时间不超过设定值,但不会。越小越好,如果设置的太小,GC的频率会增加,吞吐量会降低。控制吞吐量大小-XX:GCTimeRatio=99其值为0到100的整数,表示吞吐量。默认值为99,表示允许1%的垃圾回收时间。-XX:UseAdaptiveSizePolicy自动调整新生代的大小比例启用该参数后,JVM会根据当前系统运行状态收集监控信息,动态调整新生代的比例等。如果设置了该参数,则无需设置新生代大小、Eden、S0/S1比例等参数。3.1.2ParallelOldParallelOld是负责FullGC的老年代垃圾收集器。它是一个并行垃圾收集器。在对年老代进行排序时,是基于“标记-排序”算法。ParallelOld算法分为三个部分。Mark:将老年代的内存分成多个连续的固定大小的Region。标记存活对象后,统计每个Region中存活对象的数量。在Mark阶段,所有从GCRoots直接可达的对象被串行标记,所有存活的对象被并行标记。总结:一个Region的密度=存活对象的内存大小/Region内存大小。Summary阶段会从左到右计算每个Region的密度,然后找到一个平衡点。这个平衡点左边的Region不会进入下一个。在恢复阶段,另一边的Region需要进入下一阶段进行恢复。相当于只回收部分Region,Summary阶段是一个串行执行阶段。Compaction:利用Summary阶段的统计数据,对需要整理的部分使用“整理”算法。-XX:+ScavengeBeforeFullGCScavengeBeforeFullGC是ParallelGC中的一个参数,默认开启。它的作用是在FullGC之前触发YoungGC清理新生代,从而减少FullGC时STW的耗时。3.1.3ParallelGC调优ParallelGC会尽量满足以下目标:(优先级从高到低)MaximumPauseTimeTargetThroughputTargetMinimumMovingTarget对于ParallelGC调优,目标应该是尽量避免FullGC,这需要优化对象老化的频率。使用ParallelGC时,垃圾回收的资源开销应该小于5%。如果已经降低到百分之一甚至更少,基本就达到了极限。Survivor调优:ParallelGC可以自动调整Survivor空间。大多数程序可以使用自动调整来满足要求。个别应用程序可以关闭自动调整并在必要时执行手动调整。-Xmn1024m//新生代大小-XX:-UseAdaptiveSizePolicy//关闭自适应调整-XX:SurvivorRatio可以调整新生代中Survivor和Eden的比例,例如-XX:SurvivorRatio=6表示S(From):S(To):伊甸园=1:1:6。它的默认值是8。如果GC频率太高,整体年轻代太小,可以增加年轻代的大小,从而降低YoungGC的频率和占用时间。可以减小SurvivorRatio的值,在整个新生代不变的情况下,增加Survivor区域的大小(From和To同时增加)。一般情况下,Eden区域的大小应该比Survivor大很多。如果在一次YoungGC之后会有大量的对象被回收清理,那么新生代中Eden:From:To的比例是8:1:1比较合适。如果大部分对象的age超过1,即需要在Survivor的From和To中来回切换几次才能被回收,那么此时可以适当增加Survivor区的空间,空间在Survivor中可以使用速率增加来防止对象增长过快而被移动到老年代,从而导致FullGC。-XX:-UseAdaptiveSizePolicy//需要关闭Survivor适配-XX:TargetSurvivorRatio=
