说说JVM的垃圾回收机制

简介

垃圾回收机制英文为Garbage Collection, 所以我们常常称之为GC。那么为什么我们需要垃圾回收机制呢?如果大家有了解过Java虚拟机运行时区域的组成(JVM运行时存在,本地方法栈,虚拟机方法栈,程序计数器,堆,方法区五个区域),我们知道本地方法栈,虚拟机方法栈和程序计数器是线程独有的,即随着线程的存在而存在,随着线程的消亡而消亡,其内存分配和回收都具备确定性所以不用过多考虑回收问题。但是Java堆(主要存放对象)和方法区(存放常量和静态的属性,方法,对象)由于只有在运行时才知道有多少对象,所以这部分内存的分配和回收时动态的,此时就需要我们的垃圾回收机制来进行处理。

Point 1:如何判断对象是否存活

目前有两种方法来判断对象是否存活,第一种是引用计数算法,第二种是可达性分析算法。接下来我们对他们逐一进行讲解:

  • 引用计数算法:在对象中添加一个引用计数器,每当该对象被引用,计数器值就加一;引用失效就减一;当计数器为0,该对象就是不可使用的。虽然占用了一些额外空间,但是优点是效率高,但是存在一个问题,即:当两个对象互相引用时,虽然他们都不能被访问了,但是由于互相引用的原因,导致计数器为1,所以会成为漂浮垃圾,无法被回收。
  • 可达性分析算法:即在程序进行到某一点时,判断该对象能否被引用。我们可以举例结合图像来说明,本质上我们通过GC Root Set 来维护一个根集合,其中存在很多根对象节点,从这些节点向下走,经过的路径称为引用链,如若链中节点对根节点不可达,那么说明该对象不可使用了。该算法不能在应用程序活跃运行时执行对象追踪,因为执行上下文和对象图都在持续变化中,应用程序执行与可达性分析是一个竞态条件
  • 固定可做GC Roots 对象包括以下几种:
    • 在虚拟机栈中引用的对象
    • 在方法区中静态属性引用的对象
    • 在方法区中常量引用的对象
    • 在本地方法栈中JNI引用的对象
    • Java虚拟机内部的引用
    • 所有被同步锁持有的对象
    • 反射Java虚拟机内部情况的 JMXBean, JVMTI中注册的回调、本地代码缓存等

引用分类:

  1. 强引用
  2. 软引用
  3. 弱引用
  4. 幻影引用

JVM判断对象死亡过程

要真正宣布一个对象死亡需要精力两次标记过程,如若一个对象发现对于 GC Root 是不可达的,那么它将会被第一次标记。如若对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过了,那么虚拟机将这两者情况视作“没必要执行”。如若对象被确定为需要进行finalize()方法,那么该对象会被放到一个称为F-Queue的队列之中,并由一个新线程去执行finalize()方法。此时虚拟机还会对F-Queue队列中的对象进行一次小规模标记,如若对象在finalize()方法中成功自救,即连接上可达GC Root 对象的节点,即可逃脱回收。

需要注意的一点是:任何一个对象的finalize()方法都只会被系统自动调用一次,意味着如果第一次逃脱了,那么第二次如果被标记就会直接回收,不在进行finalize()方法。

Point 2:垃圾收集算法

主要有两类:“引用计数式” -- 直接垃圾收集 & “追踪式垃圾收集” -- 间接垃圾收集

分代收集

根据弱分代假说强分代假说,我们的收集器应该将Java堆划分出不同的区域,然后将回收对象依据其熬过垃圾收集过程的次数将其分配到不同区域中存储。所以目前我们的Java虚拟机一般存在新生代老年代。根据跨代引用假说我们可以得知跨代引用虽然存在,但是较于同代引用只占少数,因此我们引出了一个新的数据结构——记忆集,该数据结构用于吧老年代划分为若干小块,标记老年代哪一块内存存在跨代引用,如若新生代发生了GC的话,只用将对应小块内存加入到GC Root进行扫描就ok了。

注:新生代收集(Minor GC/ Young GC) / 老年代收集(Major GC/ Old GC) / 混合收集(Mixed GC) / 整堆收集(Full GC)

说了这么多关于收集器的分代概念,大家是否对虚拟机如何进行收集有疑问呢?接下来就来说说关于虚拟机的收集算法。主要分为三种:标记清除法,标记复制法,标记整理法。

  • 标记——清除算法:顾名思义,该算法分为两步,首先标记所有需要回收的对象,在标记完成后统一回收所有被标记的对象。
    • 缺点:1. 执行效率不太稳定,会随着对象数量增长降低回收(标记和回收)效率。 2. 存在内存空间碎片化问题,由于每次标记和清楚都是碎片化的操作,这样就会引出一个问题,当我们需要分配一个大的对象的时候,就无法在新生代找到一个合适的区域放置这个对象,这时会触发另外一次垃圾回收动作。
  • 标记——复制算法:为了解决上一算法存在的缺点1,我们提出了该算法,又被称为半区复制算法,即将内存容量分为两个等大区域,每次只使用其中一块,如若被使用那块被用完了就将还存活的对象迁移至另一块内存空间,这个算法也避免了上述算法的缺点2。
    • 但是同样的这个算法也存在缺点:1. 大量的内存复制开销。2. 针对大多数对象都是存存活的状况,使用标记复制算法就欠妥了。3. 将可用内存空间变为内存的一半。
    • 所以针对新生代,朝生夕灭的特点,我们可以判断出标记复制算法可以用于新生代而且由于对象存活时间短数量少,我们可以将内存空间的划分做一些改变,比如按照 8:1 的比例进行分配,选取一个占比为 1/ 9 的空间作为固定的存放存货对象的空间。但是这样就会引出一个新问题,如果超过了 1/9 空间的对象存活,那么就存放不下了。这时我们就需要内存分配担保机制将新生代无法存放的存活对象直接存入老年代。
  • 标记——整理算法:由上文可知,标记复制算法的变型用于新生代回收特别方便,那么对于我们的老年代呢?根据老年代特性,我们知道其中大部分对象都是存活的,只会有少量对象需要回收。我们可以使用标记——整理的方法来解决,即标记活的对象,然后将存活对象移至一端,然后将须回收对象移至一端,我们就只清除这一端就ok了。但是这样也带来一个问题,既然老年代大多数对象都是存活的,那么每次这样移动操作对系统消耗较大而且也会导致"Stop The World" 发生。所以我们有了一种新的办法来处理老年代对象,即让虚拟机在大多数时间都采用标记——清除算法,知道内存碎片已经影响到对象分配了,我们就采用标记——整理算法对空间进行一次整理,得到一个规整的内存空间。

Point 3:HotSpot 算法实现细节

根节点枚举

由于需要查询对象引用关系,所有的收集器在根节点枚举这一步骤都是要暂停用户线程的。由于当前主流Java虚拟机采用的都是准确式垃圾收集,用户线程停顿后不需要逐个检查执行上下文,只需得到哪些地方存储着对象引用。例如HostSopt采用的就是OopMap来记录对象信息。

安全点

在OopMap的协助下,HotStop可以快速准确的完成GC Root枚举,但是导致OopMap变化的指令很多,如若每个都生成OopMap那么将会需要大量额外空间。事实上HotSopt也只在安全点进行OopMap生成,即只有到了安全点,才可以暂停用户线程进行GC。中断也有两种方式:

  1. 抢断式中断:抢断式中断不需要线程执行代码主动配合,发生GC时,会中断所有的用户线程,如果发现有用户线程中断的地方不在安全点上,就恢复这条线程知道他到达安全点再度中断。
  2. 主动式中断:不直接对线程操作,仅仅简单的设置一个标志位,每个线程都不停的轮询这个标志位,一旦发现标志位为中断就主动挂起。这个标志位和安全点是重合的。

安全区域

可以理解为加长版的安全点。如果程序在执行,它可以运行到安全点,如果程序不执行,如线程处于Sleep或者Blocked状态,这时候线程无法响应虚拟机中断请求,自然就无法走到安全点去挂起自己。所以安全区就是在某一段代码中,引用关系不会发生变化,因此,在这个区域中任意地方开始垃圾收集是安全的。线程进入安全区域会标识自己,离开时会检查虚拟机是否完成了根结点枚举,如若没有收到就会被要求一直等待直到收到可以离开的信号。

Point 4:记忆集与卡表

在前文中我们提到了记忆集的概念,即用于解决查询跨代引用代价过大问题而引申出来的数据结构。

定义:记忆集是一种用于记录从非收集区域指向收集区域的指针集合的抽象数据结构。

有三种精度,分别是:1.字节精度,2. 对象精度, 3. 卡精度。三者中最常用的就是卡精度,顾名思义,采用卡表来实现记忆集,它定义了记忆集的记录精度与堆内存映射关系。可以联想为Java中Map。一个卡页通常包括不止一个对象,如若卡页里面有超过一个对象字段的跨代指针,那么就将对应卡表的数组元素的值设置为1,称为这个卡表变脏(dirty) 否则就是0。

卡表变脏的实际就是有其他分代区域中的对象引用了本区域的对象时,卡表就会变脏。

Point 5:写屏障

我们知道了卡表变脏时机,那么我们将如何来将卡表更新呢?由于编译过程是机器码,所以虚拟机就不方便介入,于是在我们的HotSpot中存在一个写屏障,可以看作虚拟机层面对“引用类型字段赋值”这个动作的AOP切面,在引用对象赋值时会产生一个环形通知,赋值前后都在写屏障覆盖范畴内。

一些收集器

垃圾回收器我就不过多赘述了,本质上就是对垃圾回收算法的实践。有很多版本比如 CMS, ZGC等等,如读者有兴趣可自行搜索学习。

参考资料:

《深入理解Java虚拟机》

《虚拟机设计与实现,以JVM为例》

  • 11
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。
经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Benaso

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值