《深入理解Java虚拟机》 垃圾收集器
垃圾收集器即对垃圾收集算法的实现,虚拟机规范并没有对垃圾收集器的实现做任何规定,因此不同的厂商可以自行实现。虽然可以将各个收集器放在一起进行比较,但并不能挑选出一款最好或者万能的收集器出来,只能在具体场景下选择最合适的收集器。
下图展示了7种作用于不同分代的经典收集器,图中的连线表示两个收集器可以搭配起来使用。这里说的经典是相对后面几款仍处于实验状态,但执行效果上有革命性改进的高性能低延迟收集器而言的。
1. 垃圾收集器
1.1. Serial 收集器
Serial是最基础,历史最久的收集器,曾经是JDK 1.3.1之前HotSpot虚拟机新生代收集器的唯一选择。其是一个单线程工作的收集器,并且它在进行垃圾收集时,必须暂停其他所有工作线程,直到它收集结束,也就是“Stop The World”。从JDK 1.3开始,一直到最新的JDK 13,HotSpot虚拟机团队为消除或者降低用户线程因垃圾收集而导致停顿所做的努力一直在持续,但仍然无法彻底消除。
.Serial/Serial Old 收集器过程示意图:
Serial收集器并不是老而无用,迄今为止,它依然是HotSpot虚拟机在客户端模式下新生代的默认收集器。其优点是简单而高效,对于内存受限的环境,它是所有收集器里额外内存消耗最小的。对于处理器数较少的环境,Serial没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程收集效率。
在桌面应用以及部分微服务应用中,分配给虚拟机管理的内存一般不会特别大,垃圾收集的停顿时间基本可以控制在几十毫秒左右,只要不是频繁发生收集,通常都可以接受。因此,Serial对运行在客户端模式下的虚拟机来说仍然是一个不错的选择。
1.2. ParNew 收集器
ParNew收集器实际上就是Serial的多线程并行版本,其行为以及相关的控制参数,包括对象分配规则、回收策略等都与Serial完全一致,因此它们在实现上也共用了相当多的代码。这里解释下并行与并发在垃圾收集器中的语义:
- 并行:描述的是多条垃圾收集器线程之间的关系,即同一时间存在多条垃圾收集线程在协同工作;
- 并发:描述的是垃圾收集器线程与用户线程之间的关系,即同一时间存在垃圾收集线程与用户线程同时工作;
.ParNew/Serial Old 收集器过程示意图:
ParNew一般是运行在服务端模式下HotSpot虚拟机的默认新生代收集器,尤其在JDK 1.7之前,它是除Serial之外,唯一能与CMS配合使用的收集器,因此,在使用-XX:+UseConcMarkSweepGC
激活CMS收集器后,ParNew将是默认的新生代收集器,当然,也可以使用-XX:+/-UseParNewGC
来指定或者禁用。
可以说是 CMS 的出现巩固了 ParNew 的地位,但随着更先进的 G1 收集器出现,其作为 CMS 的继承者或替代者登场,并且是一个面向全堆的收集器,因此不再需要其他新生代收集器配合。所以,从JDK 9开始,官方希望G1能完全替代服务端模式下默认的ParNew + CMS组合,并直接取消了对ParNew + Serial Old,以及Serial + CMS这两组收集器组合的支持,另外,还取消了参数-XX:+UseParNewGC
,这意味着ParNew只能与CMS一起使用,或者说将ParNew合入了CMS。
1.3. Parallel Scavenge 收集器
Parallel Scavenge也是一款新生代收集器,同样基于标记-复制算法实现,并能够多线程并行收集。其与ParNew非常相似,只是关注点不同,它的目标是达到一个可控制的吞吐量,即用户代码执行时间所占的比率。
GCTimeRatio
可以设置一个0到100之间的整数,用来控制垃圾收集时间所占的比率。默认:99,表示1/(1+99),即允许垃圾收集器占用1%的时间
MaxGCPauseMillis
允许设置一个大于0的毫秒数,收集器会尽力但不保证让垃圾回收的时间不超时设定值。但是缩短垃圾收集停顿时间是以牺牲吞吐量和新生代空间为代价的,比如系统将新生代调小一点,
那么收集肯定会更快,但是也会更频繁,停顿时间在下降,但是吞吐量也降下来了。
1.4. Serial Old 收集器
Serial Old是Serial的老年代版本,同样是一个单线程收集器,也是使用标记-整理算法。
.Serial/Serial Old 收集器过程示意图:
该收集器的意义主要供客户端模式下的HotSpot使用,如果在服务端模式下则有两种用途,一种是在JDK 5之前的版本中配合Parallel Scavenge使用,另一种则是作为CMS收集器发生Concurrent Mode Failure时的后背方案。
1.5. Parallel Old 收集器
Parallel Old是Parallel Scavenge的老年代版本,支持多线程并发收集,也是基于标记-整理算法实现。
.Parallel Scavenge/Parallel Old 收集器过程示意图:
该收集器主要是为了与Parallel Scavenge配合使用,以达到吞吐量优先的目标。在注重吞吐量或者处理器资源较为稀缺的场景下,可以优先考虑该收集器组合。
1.6. CMS 收集器
CMS是一款追求最短停顿时间为目标的收集器,有的地方也称为并发低停顿收集器,试用于一些较为关在服务响应速度的场景中。其也是基于标记-清楚算法实现的,但是过程更复杂一些,具体有下面4个阶段:
初始标记[CMS initial mark]:仅仅只是标记一下GC Roots能直接关联到的对象,速度很快,但需要Stop The World;
并发标记[CMS concurrent mark]:即从GC Roots的直接关联对象开始遍历整个对象图,耗时较长但不需要停顿用户线程;
重新标记[CMS remark]:修正并发标记期间,因为用户线程运作而导致变化的标记记录,耗时比初始标记稍长一些,但远小于并发标记阶段,不过也需要Stop The World;
并发清除[CMS concurrent sweep]:清除标记阶段已判断为死亡的对象,由于不需要移动存活对象,因此该过程也可以与用户线程并发进行;
由于整个过程中,耗时最长的并发标记与并发清除阶段,都可以与用户线程并发进行,所有总体上可以认为CMS收集器的回收过程是与用户线程一起并发执行的。
.CMS 收集器过程示意图:
但CMS收集器也有缺点,其并发阶段虽然不会导致用户线程停顿,但由于收集线程占用了处理器资源,同样会导致应用程序变慢,从而降低总吞吐量。对于这个问题,CMS默认启动(处理器核数 + 3)/4
个回收线程,这样当核数大于4时,收集器只占用不超过25%的处理器资源。
另外,由于CMS收集器在并发标记和并发清除阶段与用户线程并发执行,将会有新的垃圾不断产生,但这一部分垃圾对象是出现在标记过程结束之后,所有CMS无法处理掉它们,只能等下一次收集时再处理。对于这部分垃圾,就称为浮动垃圾。
同样由于垃圾收集阶段用户线程还在执行,因此需要预留足够的空间给用户线程,而不能像之前的收集器一样等到老年代几乎填满再进行回收。如果预留空间过小导致程序无法分配新对象,那么会引起并发失败Concurrent Mode Failures,这时JVM不得不启用后备方案,即冻结用户线程,然后临时启用Serial Old收集器来重新进行老年代的收集,这样停顿时间就长了。但是,如果预留空间过大,则又可能会导致CMS收集操作触发的太过频繁。 因此,CMSInitiatingOccupancyFraction
的值需要考虑实际情况,设置得太高可能导致大量的并发失败,而太低则可能导致收集动作频繁。其在JDK 5中,默认为68%,到了JDK 6之后改成了默认92%。
而且,由于CMS是基于标记-清除算法实现,这意味着收集之后会存在很多空间碎片。如果空间碎片过多,将会给大对象分配带来很大麻烦,
往往会出现老年代还有很多剩余空间,但无法找到足够大的连续空间来为当前对象分配,而不得不提前触发一次Full GC。
对于这个问题,CMS提供了参数UseCMSCompactAtFullCollection
(JDK 9中已经废弃),用于当CMS不得不进行Full GC时开启内存碎片的整理过程,由于这个过程必须移动存活对象,因此必须冻结用户线程,这样虽然解决了空间碎片问题,但停顿时间也更长了。另外,还提供了参数CMSFullGCsBeforeCompaction
(JDK 9中也已经废弃)用于告诉CMS在执行指定次数Full GC之后,在下一次进入Full GC之前先进行碎片整理。
1.7. G1 收集器
G1(Garbage First)收集器是一款主要面向服务端应用的垃圾收集器,它开创了收集器面向局部收集的设计思路和基于Region的内存布局形式。其最早在JDK 7中立项,但直到JDK 8 Update 40,完成了并发的类卸载支持之后,即补全了其计划功能的最后一块拼图,才被Oracle官方称为:全功能垃圾收集器。
JDK 9发布之后,官方宣布G1取代Parallel Scavenge + Serial Old,称为服务端模式下的默认收集器。另外,在规划JDK 10的功能目标时,HotSpot提出了统一垃圾收集器接口,希望将内存回收的行为与实现进行分离。由于历史原因,CMS及之前的收集器在实现中与HotSpot的内存管理、执行、编译监控等功能有着千丝万缕的关系,因此并不符合职责分离的设计原则,所以希望能基于统一的接口进行重构,这也算是为CMS退出历史舞台做最后的铺路了。
G1基于Region的内存布局形式是它能够实现停顿时间可控的关键。虽然G1也是基于分代收集理论设计的,但它不再坚持以固定大小或固定数量的分代区域进行划分,而是把连续的Java堆划分成若干大小相等的独立区域(Region),每个Region可以根据需要,选择扮演新生代的Eden,或者Survivor,以及老年代,收集器能够对扮演不同角色的Region采用不同的策略去处理,这样无论对象是新建的还是存活的,都能得到很好的处理。
虽然G1保留了新生代和老年代的概念,但它们不再是固定的了,而是一系列区域(不需要连续)的动态集合。G1会将Region作为单词回收的最小单元,具体实现时会跟踪各个Region中垃圾堆积的大小,以分析它们的回收价值,然后维护一个优先级列表,并在收集时优先处理回收价值高的Region,这也就是Garbage First名称的由来。
如果不去计算用户线程执行过程中的动作,G1收集器的运作过程大致可以分为4个步骤:
初始标记[Initial Marking]:标记一下GC Roots能直接关联到的对象,并修改TAMS指针的值,让下一阶段用户线程并发运行时,能正确地在可用的Region中分配新对象。速度很快,但需要Stop The World;
并发标记[Concurrent Marking]:从GC Roots开始对堆中对象进行可达性分析,递归扫描整个堆中的对象图,找出要回收的对象。耗时较长,但可以与用户线程并发执行。在扫描完之后,还要重新处理SATB记录下的在并发时有引用变动的对象;
最终标记[Final Marking]:对用户线程做一个短暂的暂停,用于处理并发阶段结束后,仍然遗留下来的少量SATB记录。
筛选回收[Live Data Counting and Evacuation];负责更新Region的统计数据,对各个Region的回收价值和成本进行排序,根据用户期望的停顿时间(可通过
MaxGCPauseMillis
指定,默认200ms)来制定回收计划,可以自由选择任意多个Region组成回收集,然后将存活对象复制到空的Region中,再清理掉被回收的Region空间。由于过程中涉及到对象移动,所以也需要暂停用户线程,但这里是由多个收集器线程并行完成的。
.G1 收集器过程示意图:
从Oracle官方透露,原本回收阶段也想过设计成与用户线程并发执行的,但这件事做起来比较复杂,而且考虑到G1只是回收一部分Region,停顿时间是用户可控的,所以并不那么迫切要实现。另外,G1的设计目标并不是仅仅面向低延迟,停顿用户线程能够最大幅度提高垃圾收集的效率,这样就保证了吞吐量。
对于停顿时间期望值MaxGCPauseMillis
的设置需要符合实际,毕竟G1需要冻结用户线程来负责移动对象,这个时间再怎么低也需要有个限度。一般来说,回收阶段占用几十到一百甚至两百毫秒都很正常,但如果设置得非常低,比如20ms,那么很可能出现由于期望停顿时间太短,导致每次选出来的回收集只占堆内存很小的一部分,然后收集器收集的速度逐渐跟不上分配器分配的速度,导致垃圾慢慢堆积。很可能一开始收集器还能从空闲的堆中获得一些喘息的时间,但应用运行时间一长就不行了,最终导致堆被占满而引发Full GC,反而性能降低。
从G1开始,垃圾收集器都不约而同的将设计目标改为追求能够应付应用的内存分配速率,而不再追求一次把整个Java堆都清理干净。这样,应用在分配,同时收集器在收集,只要收集的速度能跟得上对象分配的速度,那一切就能运作得很完美,这种新的收集器设计思路从工程上看就是从G1开始兴起的,比如JDK 9之后退出的ZGC收集器和Shenandoah收集器,所以说G1是收集器技术发展的一个里程碑。
2. GC 日志
2.1. GC日志参数
下面列举了一些常用的GC日志打印参数
JDK 1.8日志参数: | |
---|---|
-verbose:gc | 打印GC日志 |
-XX:+PrintGC | 效果与-verbose:gc一样,区别在于是否是稳定版本 |
-XX:+PrintGCDetails | 打印GC详细日志,并在进程退出前打印堆的详情信息 |
-XX:+PrintTenuringDistribution | 打印GC后新生代各个年龄对象的大小 |
-XX:+PrintHeapAtGC | 每次GC前后打印堆信息 |
-XX:+PrintReferenceGC | 跟踪软引用、弱引用、虚引用和Finallize队列 |
-XX:+PrintGCTimeStamps | 打印GC发生的时间 |
-XX:+PrintGCApplicationConcurrentTime | 打印进程的执行时间 |
-XX:+PrintGCApplicationStoppedTime | 打印进程由于GC而产生的停顿时间 |
-Xloggc | 指定GC日志文件,比如:-Xloggc:log/gc.log |
2.2. GC日志格式
具体每种收集器的日志形式都由其自身的实现所决定,不过为了方便使用者阅读,也维持了一定的共性,例如UseParallelGC
1 | 0.532: [GC (Allocation Failure) |
最前面的数字
0.532
、0.929
,表示GC发生的时间,是Java虚拟机自启动以来经过的秒数;开头的
[GC
、[Full GC
,表示垃圾收集的停顿类型,如果有[Full
,说明发生了Stop-The-World;后面的
[PSYoungGen
、[ParOldGen
、[Metaspace
,表示GC发生的区域,具体名称与使用的GC收集器相关;方括号内,比如
33280K->5108K(38400K)
,表示GC前使用量 -> GC后使用量(该区域内存总量)
;方括号外,比如
33280K->5652K(125952K)
,表示GC前Java堆使用量 -> GC后Java堆使容量(Java堆总容量)
;最后的时间,比如
0.0072743 secs
,表示该区域GC所用的时间,单位秒。最后面的Times中,分别表示用户消耗CPU时间、内核消耗CPU时间、操作从开始到结束所经过的墙钟时间。
墙钟时间与CPU时间的区别是它包括各种非运算的其它耗时,如等待磁盘I/O、等待线程阻塞,而CPU时间则不包括这些。另外,当系统有多CPU或者多核的话,多线程操作会叠加这些CPU时间,所以当看到user
或sys
时间超过real
也是正常的。
2.3. GC触发原因
导致GC的原因有很多,JVM中对各种原因进行了一个枚举
1 |
|
常见的导致GC的原因就是在new对象时,内存分配失败引起的,即Allocation Failure。首先进行Minor GC,对Eden区进行回收,然后将存活对象向Survivor区和老年代转移,如果发现老年代也无法分配了,那么会触发Full GC。比如下面这样
1 | {Heap before GC invocations=40 (full 2): |
2.4. GC收集器日志
下面使用在JDK 8中常用的6种不同收集器组合进行测试,分别记录启动之后的两次Full GC,以及首次Full GC之前发生的GC情况
这里发生Full GC是因为元数据空间没有指定,使用了默认值21M导致,如果设置一下MetaspaceSize
,就可以避免,只不过这里的目的就是为了看下各种收集器组合下的日志打印情况,方便分析比较。
2.4.1. UseParallelGC
Parallel Scavenge + Serial Old是默认收集器组合,其日志格式2.2中已经有过详细说明,可以通过PSYoungGen
来识别
1 | Java HotSpot(TM) 64-Bit Server VM (25.11-b03) for windows-amd64 JRE (1.8.0_11-b12), |
2.4.2. UseSerialGC
Serial + Serial Old组合收集器的日志与上面类似,可以通过DefNew
来识别
1 | Java HotSpot(TM) 64-Bit Server VM (25.11-b03) for windows-amd64 JRE (1.8.0_11-b12), |
2.4.3. UseParNewGC
ParNew + Serial Old组合收集器的日志也是类似
1 | Java HotSpot(TM) 64-Bit Server VM (25.11-b03) for windows-amd64 JRE (1.8.0_11-b12), |
2.4.4. UseParallelOldGC
Parallel Scavenge + Parallel Old组合收集器的日志也是类似的
1 | Java HotSpot(TM) 64-Bit Server VM (25.11-b03) for windows-amd64 JRE (1.8.0_11-b12), |
2.4.5. UseConcMarkSweepGC
对于Minor GC的日志与上面还是一样,这里主要看下老年代CMS收集的日志:
CMS Initial Mark,初始标记阶段,具体后面表示
[(老年代使用量) 老年代总量] (堆使用量) 堆总量
CMS-concurrent-mark,并发标记阶段,与用户线程同时执行
CMS-concurrent-preclean,重新标记阶段,上一个阶段在运行过程中,一些对象的引用已经发生了变化,然后JVM会标记堆中这个区域为Dirty Card,以及能够从Dirty Card到达的对象也会被标记,另外,做一些必要的清理操作
CMS-concurrent-abortable-preclean,这个阶段尝试去承担Final Remark阶段中足够多的工作,其持续时间依赖很多因素,由于这个阶段是重复的做相同的事情直到达到aboart的条件(比如:重复的次数、多少量的工作、持续的时间等等)之一才会停止,这个阶段在很大程度上影响着即将来临的Final Remark的停顿
CMS Final Remark,该阶段负责标记整个年老代中所有存活的对象,通常CMS会尽量在年轻代是足够干净的时候执行Final Remark阶段,目的是消除连续出现STW的情况,其中
YG occupancy:表示年轻代当前使用量和总量;
Rescan (parallel):表示重新标记存活对象所花费的时间STW;
weak refs processing:第一个子阶段,处理弱引用;
class unloading:第二个子阶段,类卸载;
scrub symbol table:第三个子阶段,清理符号表;
scrub string table:第四个子阶段,清理字符串表;
CMS-remark:最后表示收集之后,老年代和堆的使用量和总量;CMS-concurrent-sweep,并发清除阶段,与用户线程并发执行,移除已标记死亡的对象,并回收它们占用的空间
CMS-concurrent-reset,重置CMS算法内部的数据结构,为下一次CMS收集做准备
1 | Java HotSpot(TM) 64-Bit Server VM (25.11-b03) for windows-amd64 JRE (1.8.0_11-b12), |
2.4.6. UseG1GC
这里主要看下G1对年轻代收集的日志,如果触发了Full GC,则与之前也差不多
- 行11:[GC pause (G1 Evacuation Pause) (young), 0.0021238 secs]
首行按顺序分别表示:GC停顿(GC停顿的原因是疏散转移)(GC的区域是年轻代)GC总耗时
- 行12:[Parallel Time: 1.3 ms, GC Workers: 4]
由4个worker线程并行执行,耗时 1.3ms
- 行13:[GC Worker Start (ms): Min: 221.7, Avg: 221.7, Max: 221.7, Diff: 0.0]
表示GC的worker线程启动时,相对于pause开始的时间,如果Min和Max相差很大,则表示其它进程所使用的线程过多,挤占了GC的CPU时间
- 行14:[Ext Root Scanning (ms): Min: 0.2, Avg: 0.2, Max: 0.3, Diff: 0.0, Sum: 1.0]
表示扫描堆外(non-heap)的root所花费的时间,比如JNI引用, JVM的系统root等,Sum指的是CPU时间
- 行18:[Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.1, Diff: 0.1, Sum: 0.1]
扫描实际代码中的Root所花费的时间
- 行19:[Object Copy (ms): Min: 0.9, Avg: 1.0, Max: 1.0, Diff: 0.1, Sum: 3.9]
拷贝收集区内存活对象所花费的时间
- 行20:[Termination (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
GC的worker线程用了多长时间来确保自身可以安全地停止, 这段时间什么也不用做, stop之后该线程即终止运行
- 行21:[GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.1]
一些琐碎的小活动,在GC日志中不值得单独列出来
- 行22:[GC Worker Total (ms): Min: 1.3, Avg: 1.3, Max: 1.3, Diff: 0.0, Sum: 5.1]
GC的worker线程的工作时间总计
- 行23:[GC Worker End (ms): Min: 223.0, Avg: 223.0, Max: 223.0, Diff: 0.0]
GC的worker线程的完成时间,通常这部分数字应该大致相等,否则说明有太多的线程被挂起,很可能是因为坏邻居效应所导致
- 行24:[Code Root Fixup: 0.1 ms]
释放用于管理并行活动的内部数据,一般接近于零,这是串行执行的过程
- 行27:[Other: 0.7 ms]
其他活动消耗的时间, 其中有很多是并行执行的
- 行32:[Eden: 6144.0K(6144.0K)->0.0B(12.0M) Survivors: 0.0B->1024.0K Heap: 6144.0K(128.0M)->1736.4K(128.0M)]
收集前后,Eden区、Survivor区、以及整个堆的 使用量(总容量)
1 | Java HotSpot(TM) 64-Bit Server VM (25.11-b03) for windows-amd64 JRE (1.8.0_11-b12), |
3. 附录:JVM参数
这里整理一些常用的JVM参数,参数使用方式一般有两种,即
-XX:+/-<option>
:表示开启/关闭option参数-XX:<option>=<value>
:表示将option参数的值设置为value内存参数: -Xms 初始堆大小,比如:-Xms1024m -Xmx 最大堆大小,比如:-Xmx1024m。一般会将-Xmx和-Xms设置成一样,避免JVM内存自动扩展 -Xmn 新生代大小,比如:-Xmn512m。Sun官方推荐配置为整个堆的3/8 -Xss 虚拟机栈大小,比如:-Xss128k。JDK 5.0之后默认为1M,之前为256K。
减小栈可以生成更多的线程,但也不能没有限制,经验值在3000~5000左右。
对于通常的小应用,如果栈不是很深,256k应该够用了-Xoss 本地方法栈的大小,比如:-Xoss128k。
HotSpot并不区分虚拟机栈和本地方法栈,因此在HotSpot中该参数无效NewRatio 新生代与老年代的比值,比如:-XX:NewRatio=4,表示新生代/老年代=1/4,
新生代占整个堆的1/5。如果Xms = Xmx,并设置了Xmn的情况下,则此参数无效。SurvivorRatio Eden区与Survivor区的比值,默认8,表示Survivor/Eden=1/8,整个新生代的划分为8:1:1 MetaspaceSize 初始元数据空间大小,默认21M MaxMetaspaceSize 最大元数据空间大小 MinHeapFreeRatio 当堆空余比例小于MinHeapFreeRatio时,自动扩展堆直到-Xmx限制,默认40% MaxHeapFreeRatio 当堆空余比例大于MaxHeapFreeRatio时,自动压缩堆直到-Xms限制,默认70% GC参数: MaxTenuringThreshold 对象晋升到老年代的年龄,默认为15。对象每经过一次Minor GC之后,年龄就会加1 PretenureSizeThreshold 直接晋升到老年代的对象大小,单位为字节,无默认值 UseAdaptiveSizePolicy 是否动态调整Java堆中各个区域大小,以及对象进入老年代的年龄,默认开启 HandlePromotionFailure 是否允许分配担保失败,即老年代的剩余空间不足以应付新生代的整个Eden和Survivor区的所有对象都存活的极端情况,默认开启 ParallelGCThreads 用户线程冻结期间,并行GC进行内存回收的线程数 UseGCOverheadLimt 禁止GC过程无限制的执行,如果过于频繁,则直接OutOfMemory异常,默认开启 UseTLAB 优先在本地线程缓冲区中分配对象,避免内存分配时的锁定过程,server模式下默认开启 GC收集器参数: UseParallelGC 使用Parallel Scavenge + Serial Old进行垃圾回收,server模式下的默认值 GCTimeRatio GC时间占用比率,默认99,表示允许1%的时间用于GC。仅在使用Parallel Scavenge时生效 MaxGCPauseMillis GC最大停顿时间,无默认值。仅在使用Parallel Scavenge时生效 UseSerialGC 使用Serial + Serial Old进行垃圾回收,Client模式下的默认值 UseConcMarkSweepGC 使用ParNew + CMS进行垃圾回收
同时Serial Old作为CMS出现Concurrent Mode Failure失败后的备用收集器使用CMSInitiatingOccupancyFraction 老年代使用率达到多少后触发垃圾收集,默认92%。仅在使用CMS收集器时生效 UseCMSCompactAtFullCollection 不得不进行Full GC时,进行内存碎片整理,默认开启。仅在使用CMS收集器时生效 CMSFullGCsBeforeCompaction 完成多少次垃圾收集后进行一次内存碎片整理,默认为0。仅在使用CMS收集器时生效 UseParNewGC 使用ParNew + Serial Old的进行垃圾回收 UseG1GC 使用G1收集器进行垃圾回收,JDK 9之后server模式下的默认值 G1HeapRegionSize=n 设置Region大小,并非最终值 MaxGCPauseMillis 设置G1收集过程目标时间,非硬性条件,默认200ms G1NewSizePercent 新生代最小值,默认5% G1MaxNewSizePercent 新生代最大值,默认60% UseParallelOldGC 使用Parallel Scavenge + Parallel Old进行垃圾回收 即时编译参数: CompileThreshold 当函数的调用次数超过多少次时,JIT将字节码编译成本地机器码。
client模式下默认为1500,server下默认为10000线程相关参数: UseSpinning 是否启用自旋锁,默认开启 PreBlockSpin 使用自旋锁时的自旋次数,默认为10次 UseThreadPriorities 使用本地线程优先级,默认开启 UseBiasedLocking 是否启用偏向锁,默认开启 UseFastAccessorMethods 当频繁反射执行某个方法时,生成字节码以加快反射的执行速度,默认开启 调试参数: -verbose:class 打印类加载信息 TraceClassLoading 打印类加载信息,类似-verbose:class TraceClassUnloading 打印类卸载信息 HeapDumpOnOutOfMemoryError OOM时是否生成堆转储快照,默认关闭 HeapDumpPath 堆转储生成文件路径 OnOutOfMemoryError 发生OOM时的操作,比如执行脚本发送邮件
参考:
- Copyright ©《深入理解java虚拟机》
- https://www.cnblogs.com/cdfive2018/p/12320687.html
- http://47.240.95.151/2020/11/25/jvm-learning-jit/
- http://thinkhejie.github.io/2016/05/05/JVM%E7%B3%BB%E5%88%97_06/
- https://blog.csdn.net/u022812849/article/details/113728597
- https://shipilev.net/jvm/anatomy-quarks/9-jni-critical-gclocker/
- https://blog.csdn.net/FoolishAndStupid/article/details/78078238
- https://tech.meituan.com/2016/09/23/g1.html