谈谈垃圾回收器?讲讲垃圾回收算法?
包租婆:你知道市面上有哪些垃圾回收器?他们各自有什么特点?
一. 标记清除算法
-
1.主要过程: -
a.标记:先顺着 GC Roots 往下扫,保洁先从 101 开始巡查一遍所有的房间,并房间门口标记一个标识:这个房间是否可以打扫了?其他房间借的当前房间空调遥控器已经退还了吗,是否还被其他对象持有引用? -
b.清除:再把标识为可以打扫的房间挨个打扫干净。 -
2.缺点: -
两次扫描,效率低且存在空间碎片,清理出来的房间不连续 -
3.适合场景: -
老年代:老年代存活对象较多,需要标记的少,需要清除的也少。
二. 复制算法
-
1.主要过程:将可用对象拷贝到一片新的空间。把垃圾留在一片区域,从而清理的时候我放心的清理这边垃圾区。 -
2.缺点: -
空间浪费 -
增加调整引用的成本:对象移动到一片新的空间,需要调整对象的引用。使用场景:Eden 区域,大多对象朝生夕死。
三. 标记整理算法
-
1.主要过程:顺着 GC Roots 往下扫,扫到了垃圾处理,再把可用对象在空间上整理布局,保证连续。
-
2.使用场景:上文提到了标记清除有空间碎片,那么保洁是不是可以在打扫的时候,把需要打扫的房间里的租客“请”到其他的干净房间?
-
老年代:老年代存活对象较多,需要标记的少,需要清除的也少。 -
五.对象内存分配过程
new -> 尝试栈上分配 -> 尝试线程本地分配 -> 进入 eden 区 -> 默认在幸存区存活 15 次了 -> 进入老年代
-
大对象直接进入 old 区。
-
动态年龄判断:在将 s0 的对象往 s1 拷贝的时候,发现 s1 的内存已经超过了一版,那么会将年龄最大的放到老年代。
-
分配担保:在 YGC 时候,来了一个大对象但是 survivor 区空间不够了 空间担保直接进入老年代
六:查看当前 JVM 设定的参数
-XX:+PrintFlagsFinal -version
# 管道过滤
java -XX:+PrintFlagsFinal -version | grep NewRatio
七.垃圾回收器
-
Serial:年轻代串行回收,典型场景是诺基亚时代的 Java 小游戏,那时候内存小,单线程回收就行了。算法表现为:新生代的复制算法 -
Parallel Scavenge(PS):年轻代的并行回收器。 -
ParNew:年轻代Serial的多线程版本。配合 CMS 的并行回收 -
SerialOld:老年代的单线程回收器,算法为:标记整理算法 -
ParallelOld:老年代的并行回收器。 -
ConcurrentMarkSweep(CMS),并发标记清除,在垃圾回收器工作的时候业务线程也能运行。 -
G1堆空间不再物理分代,将堆空间分为若干个小region进行逻辑分代。
G1详见:
-
CMS 回收器基本执行过程:
1.初始标记:会 STW 但是也很快,只标记所有的根对象。
2.并发标记:顺着根对象往下找,在进行标记的时候工作线程也能继续运行,租客还能继续制造垃圾。
3.最终标记:STW,并发标记的时候工作线程还在跑,这次保洁要求租客先上床看看电视,暂停制造垃圾,系统只允许垃圾回收线程工作。
4.并发清除:将标记好的垃圾清除,但是存在浮动垃圾,最终标记完成之后,租客又能继续玩耍了,这段时间制造出的垃圾就是浮动垃圾。
CMS 缺点:
-
浮动垃圾
优化方案:降低触发CMS的阈值,保持老年代有足够的空间。当老年代达到70%时,触发CMS垃圾回收。
-XX:+CMSInitiatingOccupancyFraction=70
CMSInitiatingOccupancyFraction 默认值是-1, 如果 CMSInitiatingOccupancyFraction 在 0~100 之间,由 ((100 - MinHeapFreeRatio) + (double)( CMSTriggerRatio * MinHeapFreeRatio) / 100.0) / 100.0 决定。
-
空间碎片:导致对象来了没办法在老年代分配。
优化方案:启用标记整理算法
-XX:+CMSInitiatingOccupancyFraction
下期规划:
-
Redis常见数据结构及使用场景,敬请期待。