赞
踩
我们可以把垃圾回收器分为3类
串行、吞吐量优先、响应时间优先
其中,串行是单线程,吞吐量优先和响应时间优先是多线程
本文重点讨论的是分代收集器,ZGC会另外做一个文章专门讲解
Serial收集器(复制算法)——新生代
Serial Old收集器(标记-整理算法)——老年代
-XX:+UseSerialGC=Serial+SerialOld
——开启串行GC
回收过程
安全点:
GC时,所有用户线程都会在一个地方停止下来,这个地方就是安全点,目的是防止用户线程引用的内存地址因垃圾回收而混乱
Parallel Scavenge收集器(标记-复制算法)——新生代
Parallel Old收集器(标记-复制算法)——老年代
追求高吞吐量,高效利用CPU(垃圾回收时CPU占用会到100%),吞吐量一般为99%,吞吐量=用户线程时间/(用户线程时间+GC线程时间)
开启使用
-XX:+UseParallelGC — -XX:+UseParallelOldGC
——开启吞吐量优先GC,开启其中一个,另一个自动开启(JDK1.8默认开启)
-XX:+UseAdaptiveSizePolicy
——采用自适应的新生代大小调整
-XX:GCTimeRatio=ratio
——调整吞吐量的目标,垃圾回收时间不能超过总时间的(1/(1+ratio)),ratio默认为99,一般设置为19,如果达不到设定的吞吐量,就会把堆内存设置得更大,这样垃圾回收的次数就不频繁,但单次GC暂停时间就会越长
-XX:MaxGCPauseMillis=ms
——调整最大暂停时间,默认200ms,与GCTimeRatio冲突
-XX:ParallelGCThreads=n
——设置线程数
回收过程
ParNew收集器(标记-复制算法)——新生代,Serial收集器的多线程版本
CMS(Concurrent Mark Sweep)收集器(标记-清除算法)——老年代
用户线程与GC线程并发进行,用户线程与GC线程会竞争抢占CPU
开启使用
-XX:+UseConcMarkSweepGC — -XX:+UseParNewGC — SerialOld
——可以与用户线程并发执行,用于老年代,在并发开启失败后(由于内存碎片过多),会退化为串行
-XX:ParallelGCThreads=n — -XX:ConcGCThreads=threads
——ParallelGCThreads是并行的线程数,ConcGCThreads是并发的线程数,一般设置为ParallelGCThreads的1/4
-XX:CMSInitiatingOccupancyFraction=percent
——为了应对浮动垃圾,预先设置触发GC的内存阈值,一般设置为80,早期JDK默认设置为65
-XX:+CMSScavengeBeforeRemark
——在重新标记之前,要对新生代进行垃圾回收
回收过程
初始标记: 仅仅标记GC ROOTS的直接关联对象,即只标记根对象,世界暂停
并发标记: 使用GC ROOTS TRACING算法,进行跟踪标记,世界不暂停
重新标记: 因为之前并发标记,其他用户线程不暂停,可能产生了新垃圾,所以重新标记,世界暂停,且只追踪在并发标记过程中产生变动的对象
问题
定义: Garbage First(Garbage One),一种新型垃圾收集器,相比于CMS收集器,G1收集器有两个最突出的改进
发展历史
适用场景
相关JVM参数
-XX:+useG1GC
——JDK9以前
-XX:G1HeapRegionSize=size
-XX:MaxGCPauseMillis=time
新生代垃圾收集、新生代垃圾收集+并发标记、混合收集三个阶段,循环发生
第一个阶段——Young Collection(新生代)
每个region可以作为任意区域,新生成的对象会任选一个region放入,该region就会变成Eden区(简称E区)
E区资源紧张,会触发GC,把依然存活的对象放入一个新的region,该region就会变成Survivor区(简称S区),该过程会STW
如果S区资源紧张,会触发GC,把可以放入老年代的对象
放入一个新的region,该region就会变成Old区(简称O区),该过程会STW
第二阶段——Young Collection+CM(新生代垃圾回收+并发标记)
在Young GC时会进行GC Root的初始标记
老年代占用堆空间比例达到阈值时,进行并发标记(不会STW),该过程与CMS的并发标记类似,阈值由下面的JVM参数决定
-XX:InitiatingHeapOccupancyPercent=percent
——默认45
第三阶段——Mixed Collection(混合收集)
会对E、S、O区进行全面垃圾回收
最终标记(Remark)会STW,主要标记之前并发标记时漏掉的对象,这个地方也与CMS的重新标记类似
拷贝存活(Evacuation)会STW,对老年代来说,G1 会根据最大暂停时间,部分回收O区的垃圾,只回收垃圾最多的区域,这也是为什么叫Garbage First的原因
-XX:MaxGCPauseMillis=ms
——最大停顿时间
图中,橘色虚线边框的O区并没有发生GC,就是因为G1会采取Garbage First的策略
所以,G1之所以能做到既保证高吞吐量,又保证低响应时间,就是因为它并不会对老年代中所有的对象进行回收,而是选择性地进行回收,G1之所以能这样做,是基于其分区的策略,即把整个堆划分为了一个一个region。
SerialGC
ParallelGC
新生代内存不足发生的垃圾收集 - minor gc
老年代内存不足发生的垃圾收集 - full gc
CMS
新生代内存不足发生的垃圾收集 - minor gc
老年代内存不足,并发失败 - full gc
G1
新生代的根对象查找,有一部分来自老年代,老年代存活对象很多,如果挨个遍历,效率会很低,这个时候我们就会采用一种卡表技术(card table)
我们会把O区细分为很多个card(512k),如果老年代中有一个对象引用了新生代的对象,我们就把这个对象对应的card标记为脏卡,这样,新生代的根对象查找时,我们只用遍历脏卡,大大缩小了遍历区域
pre-write barrier——写屏障技术,在对象引用改变前,将对象让如队列satb_mark_queue
黑色的是已经处理完成的且有引用的对象,灰色的是在处理中的对象,白色的是还没有处理的对象
JDK 8u20 字符串去重
在新生代回收时检查是否有重复字符串,如果有,则让他们指向同一个对象
优点:节省大量内存
缺点:略微增加新生代回收时间
-XX:+UseStringDeduplication
默认开启
JDK 8u40 并发标记类卸载
所有对象都经过并发标记后,就能知道哪些类不再被使用,当一个类加载器的所有类都不再使用,则卸载它所加载的所有类
-XX:+ClassUnloadingWithConcurrentMark
默认开启
JDK 8u60 回收巨型对象
JDK 9 并发标记起始时间的调整
并发标记必须在堆空间占满前完成,否则退化为 FullGC
JDK 9 之前需要使用 -XX:InitiatingHeapOccupancyPercent
JDK 9 可以动态调整
-XX:InitiatingHeapOccupancyPercent
用来设置初始值
进行数据采样并动态调整
总会添加一个安全的空档空间
JKD 9 更高效的回收
…
之后的优化这里就不一一列出了,感兴趣的读者可以去查阅Oracle官方文档:https://docs.oracle.com/en/java/javase/12/gctuning
以上便是JVM中垃圾收集器的全部内容
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。