本文主要基于周志明老师的《深入理解Java虚拟机》。
文章目录
-
- Garbage Collection
-
- 哪些内存需要被回收?
-
- 引用计数算法 Reference Count
- 可达性分析算法 Root Searching
- 三色标记清除算法 Tri-color Marking
-
- 增量更新 Incremental Update
- 原始快照 Snapshot At The Beginning
- 垃圾回收算法
-
- 标记清除 Mark-Sweep
- 拷贝 Copying
- 标记压缩 Mark-Compact
- 标记-清除-压缩 Mark-Sweep-Compat
- 基于分代的垃圾回收
-
- JVM内存分代模型
- HotSpot中默认配置
- MinorGc、MajorGC、YGC、FGC
- 卡表 Card Table
- 垃圾收集器
-
- Serial
- Serial Old
- Parallel Old
- Parallel Scavenge
- ParNew
- CMS
- G1
- Shenandoah
- Epsilon
- ZGC
- JVM指定垃圾收集器
- 拓展阅读
Garbage Collection
自Java引入垃圾回收以来,垃圾回收器的发展就未停止过。在JDK11种引入了ZGC,在JDK12种又引入了Shenandoah。虽然新的垃圾回收器不断地涌现,但是垃圾回收的基础算法变化并不大。简单来说,回收算法主要有复制、标记清除、标记压缩。
垃圾回收可以分为三个问题:哪些内存需要被回收?什么时候去回收?如何回收?
哪些内存需要被回收?
简单的说,没有任何引用指向的一个对象或者循环引用的多个对象。需要被回收。目前垃圾回收算法主要有两类,引用计数法和可达性分析算法。JVM的垃圾回收采用了可达性分析法。
引用计数算法 Reference Count
在堆内存中分配对象时,会为对象分配一段额外空间。这段空间用于记录引用计数,当对象增加了一个新的引用,则将增加计数器的值。如果一个引用关系失效,则减少计数器的值。当一个对象的计数器的值变成0,则说明该对象已经被废弃,处于不活跃状态,可以被回收。引用计数法存在无法回收循环引用的问题,并且并发计数需要上锁效率低。Python语言中使用的便是引用计数法。
可达性分析算法 Root Searching
通过一组根对象根据引用链向下标记,没有被标记到的对象被称为不可达,证明此对象可被回收。
三色标记清除算法 Tri-color Marking
三色标记清除算法
https://www.bilibili.com/video/BV1KU4y1Y7cu
增量更新&SATB
https://www.bilibili.com/video/BV1Uz4y1S798
三色标记清除算法中用三种颜色来区分不同的内存节点。
- 白 没有遍历到的节点。
- 灰 自己标记完成,还没来得及标记子节点。
- 黑 自己标记完成,子节点都标记完成。
并发标记导致的"对象消失"问题,当且仅当以下两个条件同时满足时,会产生“对象消失”的问题,即原来应该是黑色的对象被误标成了白色。
- 赋值器插入一条或多条从黑色对象到白色对象的新引用。
- 赋值器删除了全部从灰色对象到该白色对象的直接或间接引用。
对此分别有两种解决方案,增量标记(Incremental Update)和原始快照(SATB)。
增量更新 Incremental Update
在JVM中,CMS采用的是增量标记。
增量更新破坏了第一个条件,可以简化理解为把黑色重新标记为灰色,下次重新扫描子节点。
原始快照 Snapshot At The Beginning
在JVM中,G1采用原始快照。
原始快照破坏了第二个条件,可以简化理解为无论引用关系删除与否,都会按照刚开始扫描那一刻的对象图快照来进行搜索。
垃圾回收算法
标记清除 Mark-Sweep
标记清除
位置不连续 产生碎片 效率偏低(两遍扫描)
拷贝 Copying
拷贝
没有碎片,浪费空间
标记压缩 Mark-Compact
标记压缩
没有碎片,效率偏低(两遍扫描,指针需要调整)
标记-清除-压缩 Mark-Sweep-Compat
多次GC后才压缩,是标记清除和标记压缩的结合。
基于分代的垃圾回收
基于分代的垃圾回收的本质就是分代假说。弱分代假说(weak generational hypothesis)的含义是:大多数对象都在年轻时死亡。强分代假说(strong generational hypothesis)的含义是:越老的对象越不容易死亡。
更多内容请参考《垃圾回收算法手册:自动内存管理的艺术》。
JVM内存分代模型
HotSpot虚拟机的垃圾收集器
除Epsilon、ZGC、Shenandoah之外的垃圾收集器都是使用逻辑分代模型。G1采用逻辑分代物理不分代,所以可以同时用于传统意义上的年轻代和老年代。除此之外的垃圾收集器不仅逻辑分代还物理分代。
HotSpot中默认配置
新生代 :老年代 = 1 : 3
新生代 = Eden + Suvivor0 + Suvivor1
Eden :Suvivor0 :Suvivor1 = 8 :1 :1
MinorGc、MajorGC、YGC、FGC
YGC = Young GC = MinorGC
FGC = Full GC = MajorGC
卡表 Card Table
垃圾回收跨代引用/卡表/写屏障
https://www.bilibili.com/video/BV1Jy4y1p7t8
执行YGC时,需要扫描整个老年代,效率非常低,所以JVM设计了CardTable。如果一个老年代CardTable中有对象指向年轻代,就将它设为Dirty,下次扫描时,只需要扫描Dirty Card。
在数据结构上,Card Table用BitMap来实现。
垃圾收集器
Serial
Serial是一个stop-the-world,基于拷贝算法的单GC线程年轻代垃圾收集器。
Serial Old
Serial Old是一个stop-the-world,基于mark-sweep-compact(MSC)算法的单GC线程老年代垃圾收集器。
Parallel Old
Parallel Old是一个stop-the-world,基于压缩算法的多GC线程老年代垃圾收集器。
Parallel Scavenge
Paralledl Scavenge是一个stop-the-world,基于拷贝算法的多GC线程年轻代垃圾收集器。
ParNew
在ParNew在Paralledl Scavenge之后诞生,所以叫Parallel New。
Parallel Old是一个stop-the-world,基于压缩算法的多GC线程老年代垃圾收集器。
他和Paralledl Scavenge的区别是它做了增强使他可以和CMS配合使用。
CMS
Concurrent Mark Sweep
算法:三色标记 + Incremental Update
老年代
垃圾回收和应用程序同时运行,降低STW的时间(200ms)
CMS问题比较多,所以现在没有一个版本默认是CMS,只能手工指定
一个大多数并发、低停顿的收集器
CMS既然是MarkSweep,就一定会有碎片化的问题,碎片到达一定程度,CMS的老年代分配对象分配不下的时候,使用SerialOld 进行老年代回收
4阶段
初始标记 initial mark (STW)
并发标记 concurrent mark
重新标记 remark (STW)
并发清除 concurrent sweep
G1
算法:三色标记 + SATB(Snapshot-At-The-Beginnin)
Shenandoah
算法:ColoredPointers + WriteBarrier
Epsilon
Epsilon不回收内存,用来做测试的。
ZGC
算法:ColoredPointers + LoadBarrier
JVM指定垃圾收集器
-XX:+UseSerialGC = Serial New (DefNew) + Serial Old
-XX:+UseParNewGC = ParNew + SerialOld(某些版本已废弃)
-XX:+UseConcMarkSweepGC = ParNew + CMS + Serial Old
-XX:+UseParallelGC = Parallel Scavenge + Parallel Old (1.8默认)
-XX:+UseParallelOldGC = Parallel Scavenge + Parallel Old
-XX:+UseG1GC = G1
拓展阅读
《垃圾回收算法手册:自动内存管理的艺术》.https://book.douban.com/subject/26740958
《新一代垃圾回收器ZGC设计与实现》.https://book.douban.com/subject/34812818/