JVM(五) GC 底层细节
一、三色标记
在三色标记之前有个算法叫 标记 - 清除 法,这个标记会根据这个对象是否可达(GC Roots)来进行一个标记设置,GC 回收的时候就会根据这个标记进行清除。
标记 - 清除 法最大的问题就是在清除的-时候需要 STW 这就导致了 GC 的效率就比较低。
三色标记最大的好处就是可以与业务线程并发执行,从而显著的提升标记的效率,三色 对应黑、白、灰三种颜色,每种颜色都代表着不同的意思:
黑色:根对象或它所有的子对象都以及被扫描过。
灰色:本身被扫描但是它的子对象还没被扫描完。
白色:没有被扫描过的对象,如果扫描标记完成后还是白色的说明这个对象是垃圾。
三色标记问题
因为在标记的时候业务线程还在继续运行,那么就会存在当 GC 其中一条线程以及完成标记扫描后与其他一条线程未被扫描完成后的对象发生了引用的改变。
CMS 与 G1 都有不同的解决方案,但是对比来说还是 G1 的效率是比较高的。
CMS 解决方案
当 C 对象被一个黑色对象所引用,那么 A 对象就会被修改为灰色,而 B 则会被标记为黑色,让垃圾回收器重新标记这个对象链上的所有对象,这个算法为 Incremental Update 算法。
G1 解决方案
在开始 GC 之前先在 Region 中存放一个快照,当 B 和 C 的引用断开之后则会把这个引用推到 GC 堆栈中(灰色对白色对象的引用),以确保 C 对象还能被扫描到,下次扫描的时候(最终标记)就能扫描到这个 C 对象了(以这个对象的引用为根进行重新扫描)。这个算法为 STAB(snapshot-at-the-beginning) 算法。
最终标记:对所有业务线程进行暂停,处理那些在并发标记的时候漏标的对象。
CMS 和 G1 对比
CMS 关注的是对象引用的增加。
G1 关注的是对象的引用的删除。
CMS 是重新扫描所有的对象,而 G1 则是以修改的引用作为根进行扫描的所以 CMS 对比 G1 来说效率是比较低的。
二、跨代引用
跨代引用 一般指的是老年代的对象中引用了新生代的对象,如果新生代发生 Young GC 的时候就需要扫从老年代到新生代的所有引用,这样的扫描就比较消耗性能,所以就存在了Card Table(卡表) 和 Rset(记忆集)。
CardTable
当发生新生代 GC 的时候需要扫描整个老年代,效率很低,所以 JVM 就设计了 CardTable,类似于一个数组,数组中的每个元素就对应了每个内存区域。当某个区域发生了跨代引用的话那么就会把这个区域的设置为 Dirty(标记修改为 1 ),每个数组的元素对应了一个个老年代的内存区域,而这一个个数组元素被称为“卡页”,一般来说卡页的大小都是 2 次幂。被标记为 1 的卡页就会存放一个记录跨代引用的记忆集(RSet)。
RSet
记录了其他 Region 区到当前 Region 区的引用,这个的存在就可以在扫描老年代的时候只扫描这个里面所存储的对象引用就行了。
G1 与 CMS 对比
其实在 CMS 中也有类似的处理方式,只是在 G1 中是每个 Region 中都会存放一个 RSet ,但是在 CMS 中只有一块内存区域,那么就导致了 G1 的 RSet 占用的内存过多,而 CMS 只会存在一个老年代,所以在选择 GC 时需要考虑当前堆内存的大小。
三、安全点、安全区域
在 GC 开始工作需要暂停当前业务所有线程的时候,并不是强制停止所有的业务线程的,而是会修改一个主动式中断标记为中断,而所有的业务线程会去轮询查询这个标记来判断是否需要停止当前的线程。
安全点
当业务线程发现需要中断时,那么业务线程就会跑到最近的一个安全点进行挂起,而这个安全点就可能是方法的调用、循环的跳转、异常的跳转。
安全区域
当然只有在跑动的线程才会区轮询查询这个标记,但是当线程 Sleep 活着 Blocked 状态的话,那么线程就不知道需不需要挂起当前线程。那么就会看这个代码是否在这个安全区内(区间不会发生引用的变动),所以 GC就不用管这些线程,可以进行 GC 了。当然,在 GC 没有完成根节点枚举的时候这些线程即使已经完成了也需要进行挂起,等待 GC 完成后继续进行下一步。
四、GC日志
GC常用参数
Xmn -Xms -Xmx –Xss 年轻代 最小堆 最大堆 栈空间
-XX:+UseTLAB 使用 TLAB,默认打开
-XX:+PrintTLAB 打印 TLAB 的使用情况
-XX:TLABSize 设置 TLAB 大小
-XX:+DisableExplicitGC 启用用于禁用对的调用处理的选项 System.gc() -XX:+PrintGC 查看 GC 基本信息
-XX:+PrintGCDetails 查看 GC 详细信息
-XX:+PrintHeapAtGC 每次一次 GC 后,都打印堆信息
-XX:+PrintGCTimeStamps 启用在每个 GC 上打印时间戳的功能
-XX:+PrintGCApplicationConcurrentTime 打印应用程序时间(低) -XX:+PrintGCApplicationStoppedTime 打印暂停时长(低)
-XX:+PrintReferenceGC 记录回收了多少种不同引用类型的引用(重要性低)
-verbose:class 类加载详细过程
-XX:+PrintVMOptions 可在程序运行时,打印虚拟机接受到的命令行显示参数
-XX:+PrintFlagsFinal -XX:+PrintFlagsInitial 打印所有的 JVM 参数、查看所有 JVM 参数启动的初始值(必须会用)
-XX:MaxTenuringThreshold 升代年龄,最大值 15, 并行(吞吐量)收集器的默认值为 15,而 CMS 收集器的默认值为 6。
Parallel 常用参数
-XX:SurvivorRatio 设置伊甸园空间大小与幸存者空间大小之间的比率。默认情况下,此选项设置为 8
-XX:PreTenureSizeThreshold 大对象到底多大,大于这个值的参数直接在老年代分配
-XX:MaxTenuringThreshold 升代年龄,最大值 15, 并行(吞吐量)收集器的默认值为 15,而 CMS 收集器的默认值为 6。
-XX:+ParallelGCThreads 并行收集器的线程数,同样适用于 CMS,一般设为和 CPU 核数相同
-XX:+UseAdaptiveSizePolicy 自动选择各区大小比例
CMS常用参数
-XX:+UseConcMarkSweepGC 启用 CMS 垃圾回收器
-XX:+ParallelGCThreads 并行收集器的线程数,同样适用于 CMS,一般设为和 CPU 核数相同
-XX:CMSInitiatingOccupancyFraction 使用多少比例的老年代后开始 CMS 收集,默认是 68%(近似值),如果频繁发生 SerialOld 卡顿,应该调小,(频繁 CMS 回
收)
-XX:+UseCMSCompactAtFullCollection 在 FGC 时进行压缩
-XX:CMSFullGCsBeforeCompaction 多少次 FGC 之后进行压缩
-XX:+CMSClassUnloadingEnabled 使用并发标记扫描(CMS)垃圾收集器时,启用类卸载。默认情况下启用此选项。
-XX:CMSInitiatingPermOccupancyFraction 达到什么比例时进行 Perm 回收,JDK 8 中不推荐使用此选项,不能替代。
-XX:GCTimeRatio 设置 GC 时间占用程序运行时间的百分比(不推荐使用)
-XX:MaxGCPauseMillis 停顿时间,是一个建议时间,GC 会尝试用各种手段达到这个时间,比如减小年轻代
G1常用参数
-XX:+UseG1GC 启用 CMS 垃圾收集器
-XX:MaxGCPauseMillis 设置最大 GC 暂停时间的目标(以毫秒为单位)。这是一个软目标,并且 JVM 将尽最大的努力(G1 会尝试调整 Young 区的块数来)来实
现它。默认情况下,没有最大暂停时间值。
-XX:GCPauseIntervalMillis GC 的间隔时间
-XX:+G1HeapRegionSize 分区大小,建议逐渐增大该值,1 2 4 8 16 32。随着 size 增加,垃圾的存活时间更长,GC 间隔更长,但每次 GC 的时间也会更长
-XX:G1NewSizePercent 新生代最小比例,默认为 5%
-XX:G1MaxNewSizePercent 新生代最大比例,默认为 60%
-XX:GCTimeRatioGC 时间建议比例,G1 会根据这个值调整堆空间
-XX:ConcGCThreads 线程数量
-XX:InitiatingHeapOccupancyPercent 启动 G1 的堆空间占用比例,根据整个堆的占用而触发并发 GC 周期
五、低延迟垃圾回收器
传统的垃圾回收器一般情况下内存、吞吐量、延迟只能满足其中的两个,但随着目前的科技发展以 延迟 成为主要发展目标,所以就有现在的低延迟的垃圾回收器。
Eplison
这个垃圾回收器不回收垃圾,主要用于压力测试,可以查看堆的布局、对象分配等等相关信息。
ZGC
有类似于 G1 的 Region,但是没有分代。
标志性的设计是染色指针 ColoredPointers(涉及的东西较多),染色指针有 4TB 的内存限制,但是效率极高,它是一种将少量额外的信息存储在指针上 的技术。 它可以做到几乎整个收集过程全程可并发,短暂的 STW 也只与 GC Roots 大小相关而与堆空间内存大小无关,因此可以实现任何堆空间 STW 的时间小于 十毫秒的目标。
Shenandoah
第一款非 Oracle 公司开发的垃圾回收器,有类似于 G1 的 Region,但是没有分代。
也用到了染色指针 ColoredPointers。 效率没有 ZGC 高,大概几十毫秒的目标。