【JVM】你真的了解垃圾回收吗

在这里插入图片描述

前言

很多人想到垃圾回收,第一反应的想到的是

  • 如何判断对象已经死亡?
  • 有哪些垃圾收集算法?
  • 常见的垃圾收集器?

  • 这么经典的题目肯定是难不倒你的,但是其中的很多具体事项却更要值得我们注意。文章以HotSpot可达性分析算法来带读者了解一二。
    文章涉及知识点较多,但是深度都点到为止,读者若想深入了解,再自行翻阅资料。
    在这里插入图片描述

哪些对象可以作为CG Roots呢?

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象
  • 本地方法栈(Native方法)中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 所有被同步锁持有的对象

那我们该如何枚举所有的Roots对象呢?

在这些Roots中,更多的主要是在全局性的引用(例如常量或静态属性)与执行上下文(例如栈帧中的本地变量表)中,现在很多的应用仅仅方法区就有数百兆,如果要逐个检查里面的引用,那么必然要消耗很多时间。同时,目前所有收集器在枚举根节点这一步骤都是必须暂停用户线程的(Stop The World)。

那我们应该怎样更快的枚举根节点呢?
目前主流的Java虚拟机使用的都是准确式GC(即虚拟机可以直接知道哪些地方存放这对象引用,不需逐个检查),所以当执行系统停顿下来后,并不需要一个不漏地检查完所有执行上下文和全局的引用位置,虚拟机应当是有办法直接得知哪些地方存放这对象引用的。

OopMap

HotSpot的实现中,是使用一组称为OopMap的数据结构来达到这个目的。

在类加载动作完成的时候,HotSpot就会把对象内什么偏移量上是什么类型的数据计算出来。
在即时编译过程中,也会在特定的位置记录下栈里和寄存器里哪些位置是引用。
这样收集器在扫描时就可以直接得知这些信息,并不需要真正一个不漏地从方法区等GC Roots开始查找。

所以OopMap的作用就是存储内存中哪些位置存储了对象引用。

安全点SafePoint

生成OopMap的位置称为安全点(SafePoint),程序执行到安全点则停下来开始GC。安全点的位置一般是程序长时间执行的指令位置
1、循环的末尾
2、方法临返回前 / 调用方法的call指令后
3、可能抛异常的位置等。

安全区域Safe Region

主要针对没有分配CPU时间的线程,如线程处于Sleep状态或者Blocked状态。这个时候线程无法响应JVM的中断请求。所以需要安全区域来解决。
指在一段代码片段中,引用关系不会发生变化。在这个区域中任意地方开始GC都是安全的。也可以把Safe Region看作是被扩展了的Safepoint。

知道了Roots后该怎么进行标记

三色标记法

三色标记法将对象的颜色分为了黑、灰、白,三种颜色。

  • 黑色:该对象已经被标记过了,且该对象下的属性也全部都被标记过了。(程序所需要的对象)
  • 灰色:该对象已经被标记过了,但该对象下的属性没有全被标记完。(GC需要从此对象中去寻找垃圾)
  • 白色:该对象没有被标记过。(对象垃圾)

首先是初始状态,很简单,只有GC Roots是黑色的。同时需要注意下面的图片的箭头方向,代表的是有向的,比如其中的一条引用链是:
根节点->5->6->7->8->11->10
在这里插入图片描述
在扫描的过程中,变化是这样的:
在这里插入图片描述
你看上面的动图,灰色对象始终是介于黑色和白色之间的。当扫描顺利完成后,对象图就变成了这个样子:
在这里插入图片描述
此时,黑色对象是存活的对象,白色对象是消亡了,可以回收的对象。

并发标记带来的问题与解决

首先,为什么要有并发标记呢?
就是为了消减“标记”阶段的世界,那就是让垃圾回收器和用户线程同时运行,并发工作。也就是CMS和G1收集器都有的并发标记的阶段。
在这里插入图片描述

同时,并发标记也会带来两个问题:

浮动垃圾和漏标

  • 浮动垃圾:一种是把原本消亡的对象错误的标记为存活,这不是好事,但是其实是可以容忍的,只不过产生了一点逃过本次回收的浮动垃圾而已,下次清理就可以。

  • 漏标(对象消失):一种是把原本存活的对象错误的标记为已消亡,这就是非常严重的后果了,一个程序还需要使用的对象被回收了,那程序肯定会因此发生错误。

浮动垃圾影响不大,主要来讲讲漏标问题。
情况一
在这里插入图片描述
情况二
在这里插入图片描述
那要怎么解决呢?

增量更新与原始快照

增量更新(Incremental Update)和原始快照(Snapshot At The Beginning,SATB)两种解决方案。

在HotSpot虚拟机中,CMS是基于增量更新来做并发标记的,G1则采用的是原始快照的方式。

什么是增量更新呢?

增量更新要破坏的是第一个条件(赋值器插入了一条或者多条从黑色对象到白色对象的新引用),当黑色对象插入新的指向白色对象的引用关系时,就将这个新插入的引用记录下来,等并发扫描结束之后,再将这些记录过的引用关系中的黑色对象为根,重新扫描一次。

可以简化的理解为:黑色对象一旦插入了指向白色对象的引用之后,它就变回了灰色对象。

什么是原始快照呢?

原始快照要破坏的是第二个条件(赋值器删除了全部从灰色对象到该白色对象的直接或间接引用),当灰色对象要删除指向白色对象的引用关系时,就将这个要删除的引用记录下来,在并发扫描结束之后,再将这些记录过的引用关系中的灰色对象为根,重新扫描一次。

这个可以简化理解为:无论引用关系删除与否,都会按照刚刚开始扫描那一刻的对象图快照开进行搜索。

写屏障

增量更新用的是写后屏障(Post-Write Barrier),记录了所有新增的引用关系。

原始快照用的是写前屏障(Pre-Write Barrier),将所有即将被删除的引用关系的旧引用记录下来。

Reference
https://www.cnblogs.com/plxx/p/4217812.html
https://juejin.cn/post/6859931488352370702
https://segmentfault.com/a/1190000021820577

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值