JVM系列文章目录
细谈JVM垃圾回收与部分底层实现
前言
本文基于JDK1.8,是博主个人的JVM学习记录,欢迎各位指正错误的地方
并发标记与三色标记
在前面对文章中我们有聊到CMS垃圾回收器和G1垃圾回收器标记阶段都有并发标记到过程,并发标记的实现就要依赖这个三色标记。
三色标记
先来介绍一下三色标记的三种颜色代表的意思
- 黑色:表示根对象,或者该对象及其子对象已被扫描过了;
- 灰色:表示该对象已被扫描了,但是子对象还未被扫描到;
- 白色:该对象还未被扫描;
通过这种三色标记,就可以多个线程同时标记,不用担心彼此标记被影响,在垃圾回收的时候只要回收白色的对象就可以;但是三色标记也是有缺陷的,它会出现漏标的问题。
漏标的原因
我们通过图解的方式来大致介绍一下漏标的过程:
假设我们有多个线程在进行GC回收的并发标记
上图可以看出线程1已经完成基本的扫描了,只要等最后回收就行了,但是
B对象对C对象的引用发生了变化,B对象不引用C对象了,改为A对象引用C对象了,C对象按理来说也是根可达的,应该被扫描并标记为黑色,但是线程1已经完成扫描了,单独依赖三色标记法没办法解决C漏标的问题。
为解决这个问题,在CMS和G1中分别提供了不用的解决方案。
CMS中解决漏标问题的方案
在CMS中使用Incremental Update 算法 来解决三色标记导致的漏标问题。大致思路是这样的:当一个白色对象 被黑色对象引用时,将这个黑色对象标记为灰色对象,并让线程重新从根对象开始扫描。因为有从根对象重新扫描的关系,所以速度比较慢。
G1中解决漏标问题的方案
G1使用SATB(snapshot-at-the-beginning),在开始的时候做一个快照,当上图中B、C之间的引用发生变化时,直接将这个引用推到GC的堆栈中(没错,GC也使用堆栈,GC方法运行时数据也是来自栈中的,GC本质也是线程,也要占用一小部分的堆栈,我们也可以将它当作一个普通java线程来理解就行了),这样我下次扫描(最终标记的时候)的时候直接就能扫到C对象。
G1和CMS解决漏标问题的区别
- CMS关注引用的增加,关注的是A到C新增这条引用;G1关注引用的删除,关注B到C减少的这条引用。
- CMS重新扫描要从根对象开始扫,STW时间长,G1只要从变更的引用对象开始扫就可以了,STW时间短,更高效。
G1中的部分技术细节
G1的内存区域不固定
因为G1使用的是一个个Region,每个Region都可摇身一变在Eden、Survivor、Old、Humongous之间来回切换身份(这个在JVM分代回收机制和垃圾回收算法有介绍),通过这种切身份的方式可以让垃圾回收更高效,比如说Region开始是Eden,发生新生代垃圾回收后,直接在自己内部复制删除,然后把自己变成Survivor就行了。
跨代引用与CradTable、Rset
跨代引用一般指的是老年代跨代对象引用新生代的对象,这个时候这个新生代没办法确定这个对象是不是死了,但是又不可能扫描整个整个老年代(扫描整个老年代效率也太低了吧),所以G1通过CradTbale(卡表)和Rset(记忆集)来解决这个跨代引用的问题。
CradTbale
- CradTable实际上就是一个数组,它每一个下表都代表堆中间一片连续的内存区域,比如crad[0]表示为 0x0000~0x03FF这片内存区域、crad[1]0x0400 ~ 0x07FF。
- CradTable数组只有两种值:0/1,0表示该区域没有跨代引用,1表示该区域有一个或多个跨代引用。这样垃圾回收器在垃圾回收的时候只要根据下表扫描对应一小片区域的对象就可以了。
- CradTable在堆中只需要一份,它可以记录整个堆的跨代引用情况。
Rset
- Rset实际上就是记录引用的集合(记录了其他 Region 中的对象到本 Region 的引用)。
- 它可以理解成一个key-value的格式,前面的key表示的对应的CradTable的数组下表,value存储的是引用,这样谁引用了当前Region中的对象,直接扫描Rset就可以了。
- ReSet本身就是一个Hash表,如果是在 G1 的话,则是在一个新生代的 Region 区里面,这样的话其实内存消耗很大的,多的时候能达到JVM内存空间的20%,所以G1真的不是小内存的机器玩得起的(CMS也有类似处理)。
安全点和安全区域
在介绍安全点和安全区域的之前先声明一点,JVM的GC不是强制性的(不是JVM要垃圾回收了,直接让业务线程统统挂起),它是主动式的(主动指的是业务线程,业务线程自己乖乖挂起),通常是GC线程设置一个状态值,告诉业务线程我要收垃圾了,业务线程回去轮询这个状态值,发现GC要收垃圾了,而自己刚好又处于安全点,就进行GC。
安全点
安全点其实可以理解成业务线程暂停的时候对象引用不会发生变化的节点(确保用户线程暂停的这行字节码指令是不会导致引用关系的变化)。比如方法调用、循环跳转、异常跳转等,一般是这些指令才会产生安全点。
安全区域
需要安全区域这个概念的原因是:有的时候业务线程可能不在运行(业务线程处于 Sleep 或者是 Blocked 状态),业务线程没办法自己到达安全点。
安全区域是指能够确保在某一段代码片段之中, 引用关系不会发生变化,因此,在这个区域中任意地方开始垃圾收集都是安全的。我们也可以把安全区域看作被扩展拉伸了的安全点。
如果业务线程在垃圾回收中途“醒了”,它也要确定 JVM 是否已经完成了根节点枚举,或者其他 GC 中需要暂停用户线程的阶段。
低延迟的垃圾回收器
垃圾回收器三项指标
传统的垃圾回收器一般只能在内存占用、吞吐量、延时 同时满足两个。但是现在的发展,延迟这项的目标越来越重要。所以就有低延迟的垃圾回收器。
Eplison
- 这个垃圾回收器不能进行垃圾回收,是一个“不干活”的垃圾回收器,由 RedHat 推出。
- 它负责堆的管理与布局、对象的分配、与解释器 的协作、与编译器的协作、与监控子系统协作等职责,主要用于需要剥离垃圾收集器影响的性能测试和压力测试 (不是大佬真的用不来)。
ZGC
- 有类似于 G1 的 Region,但是没有分代。
- 标志性的设计是染色指针 ColoredPointers,染色指针有 4TB 的内存限制,但是效率极高,它是一种将少量额外的信息存储在指针上 的技术。 它可以做到几乎整个收集过程全程可并发,短暂的 STW 也只与 GC Roots 大小相关而与堆空间内存大小无关,因此考科一实现任何堆空间 STW 的时间小于 十毫秒的目标(这个收费的,大部分公司也用不上)。
Shenandoah
- 第一款非 Oracle 公司开发的垃圾回收器,有类似于 G1 的 Region, 但是没有分代。
- 也用到了染色指针 ColoredPointers。 效率没有 ZGC 高,大概几十毫秒的目标。
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
-XX:PreTenureSizeThreshold
-XX:MaxTenuringThreshold
-XX:+ParallelGCThreads
-XX:+UseAdaptiveSizePolicy
CMS
常用参数
-XX:+UseConcMarkSweepGC
-XX:+ParallelGCThreads
设置伊甸园空间大小与幸存者空间大小之间的比率。默认情况下,此选项设置为 8
大对象到底多大,大于这个值的参数直接在老年代分配 升代年龄,最大值 15, 并行(吞吐量)收集器的默认值为 15,而 CMS 收集器的默认值为 6。 并行收集器的线程数,同样适用于 CMS,一般设为和 CPU 核数相同 自动选择各区大小比例
启用 CMS 垃圾回收器 并行收集器的线程数,同样适用于 CMS,一般设为和 CPU 核数相同 -XX:CMSInitiatingOccupancyFraction 收)
-XX:+UseCMSCompactAtFullCollection
-XX:CMSFullGCsBeforeCompaction
-XX:+CMSClassUnloadingEnabled
-XX:CMSInitiatingPermOccupancyFraction
使用多少比例的老年代后开始 CMS 收集,默认是 68%(近似值),如果频繁发生 SerialOld 卡顿,应该调小,(频繁 CMS 回
在 FGC 时进行压缩 多少次 FGC 之后进行压缩 使用并发标记扫描(CMS)垃圾收集器时,启用类卸载。默认情况下启用此选项。 达到什么比例时进行 Perm 回收,JDK 8 中不推荐使用此选项,不能替代。
-XX:GCTimeRatio
-XX:MaxGCPauseMillis
G1 常用参数
设置 GC 时间占用程序运行时间的百分比(不推荐使用) 停顿时间,是一个建议时间,GC 会尝试用各种手段达到这个时间,比如减小年轻代
-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 周期
上一篇:JVM分代回收机制和垃圾回收算法