首页 > 编程语言 >深入理解Java虚拟机 --- 垃圾标记/收集算法

深入理解Java虚拟机 --- 垃圾标记/收集算法

时间:2024-11-08 13:20:35浏览次数:1  
标签:Java 标记 对象 虚拟机 --- 算法 GC 引用 垃圾

在开始本章之前,我们得了解一个概念,那就是我们怎么知道这个对象是"垃圾"?所以如何定义垃圾就成为我们第一个需要探讨的重要的点之一。

垃圾标记算法

常见的垃圾标记算法有:引用计数算法可达性分析算法

引用计数算法

实现思路

每个对象去额外存储一个引用计数器,这个计数器统计了对象被引用的次数,当被引用的次数为0时,就可以认为它是垃圾了。

优点

实现简单,垃圾对象便于识别;判定效率高,回收没有延迟性。

缺点

它有一个致命的缺陷,导致了它这个算法没有被采用。那就是它解决不了循环依赖(或者说解决的成本太高了)。

这个问题导致的直接问题就是--内存泄漏

image.png

可达性分析算法

GC Roots

GC Roots的对象包含以下几种:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象。(可以理解为:引用栈帧中的本地变量表的所有对象)

  • 方法区中静态属性引用的对象(可以理解为:引用方法区该静态属性的所有对象)

  • 方法区中常量引用的对象(可以理解为:引用方法区中常量的所有对象)

  • 本地方法栈中(Native方法)引用的对象(可以理解为:引用Native方法的所有对象)

当然随着用户选用的垃圾回收器以及当前区域的不同,也可能有其他对象"临时性"地加入GC Roots。

谈谈引用

  • 强引用:我们平常用new创建的对象所获得的引用就是强引用。被强引用引用的对象,无论发生什么都不会垃圾回收

  • 软引用:软引用主要用来描述一些还有用,但非必须的对象。只被软引用关联的对象,在系统即将要发生OOM时,会被垃圾回收

  • 弱引用:弱引用主要用来描述一些非必须对象。所有被弱引用关联的对象,只能生存到下一次垃圾回收发生为止。

  • 虚引用:虚引用不会影响对象的存活时间,也无法通过虚引用来获取对象实例。唯一作用就是能在这个对象被收集器回收时收到一个系统通知。

生与死 & finalize()

如果在可达性分析算法中被认定为不可达对象,也不是"非死不可",他们处于缓刑状态,这时候会调用finalize()方法(如果对象重写了该方法那么执行重写方法),这个是他活路的唯一时机。如果在finalize()还没有重新建立引用,那么下一次标记为不可达时,那它必定会死。

要真正宣告一个对象的死亡,至少要经历两次标记。

实现思路

  • 可达性分析算法是以根对象集合(GC Roots)为起始点,按照从上到下的方式搜索被根对象集合所连接的目标对象是否可达

  • 使用可达性分析算法后,内存中存活的对象都会直接或间接跟根对象集合连接,搜索所走过的路径称为引用链

  • 如果目标对象没有任何引用链相连,则是不可达的,意味着目标对象已死亡,可以标记为垃圾对象。

  • 只有被根对象集合直接或间接连接的对象才是存活对象。

image.png

并发的可达性分析--三色标记法

思想

将对象根据状态标记为黑、灰、白三种颜色。

:该对象没有被标记过,为垃圾。(当然最开始都是白色的)

:该对象已经标记过了,但是该对象下的引用还没有标记完。

:该对象已经标记过了,且该对象下的引用也被标记过了。

算法流程

1、初始状态:先把所有对象都标记为白色。

2、遍历根对象:从根对象(GC Root)开始遍历,遍历对象时,将其标为灰色并放在专门的灰色集合中。

3、遍历灰色集合中的对象:从灰色集合中取出对象,并遍历该对象的引用对象,如果引用对象是白色的,把其标记为灰色,并放入灰色集合中;反之,则不做处理。

4、上述3操作遍历完引用对象后的灰色对象会被标记为黑色,并放在专门的黑色集合中。

5、反复进行3的操作直到灰色集合为空,最后仍然为白色的对象就表明其为垃圾。

问题

主要会出现两种问题:漏标和浮动垃圾。

浮动垃圾的问题还能容忍,因为在下一次GC就能够把浮动垃圾给收集了,主要影响的是下一次GC的时间。

但是漏标的问题就很大了,它会导致"对象消失"

  • 发生的条件

    • 1、赋值器插入了一条或多条从黑色对象到白色对象的引用。

    • 2、赋值器删除了全部从灰色对象到该白色对象的直接或间接引用。

漏标的手绘图

image.png

image.png

image.png

image.png

这样白色对象就会成为消失的对象。

解决漏标问题

有两种解决方案:增量更新和原始快照。

增量更新

主要破坏的是:赋值器插入了一条或多条从黑色对象到白色对象的引用。

实现思路:当黑色对象引用一个白色对象时,需要记录该黑色对象,等并发扫描结束后,再以他为根去重新扫描一次。

原始快照

主要破坏的是:赋值器删除了全部从灰色对象到该白色对象的直接或间接引用。

实现思路:当灰色都想要删除白色对象的引用关系时,就要将这个白色对象记录下来。并发扫描结束后,会将记录下来的白色对象标记为灰色,然后以他们为根,重新扫描一次。

两者比较

两者相比:原始快照用产生浮动垃圾的可能性,减少了需要重新扫描的时间。(空间换时间)

垃圾收集算法

上述我们已经成功把垃圾给标出来了,那么我们应该重点去思考我们应该怎样优雅的收集垃圾。

分代收集理论

这个理论是基于三个重要的假说上的:

1、弱分代假说:绝大多数的对象都是朝生夕死的。

2、强分代假说:熬过多次越多次垃圾收集的对象越难以消亡。

3、跨代引用假说:跨代引用的对象相对于同代引用来说仅占少数。

基于上述三个假说,我们把堆分成两个区域:年轻代(Eden、Survivor1区、Survivor2区)、老年代。

记忆集(卡表)

为了解决对象跨代引用所带来的问题,垃圾回收器在新生代中建立了名为记忆集的数据结构,用以避免把整个老年代加进GC Root扫描范围。

记忆集(卡表):只需要记录非收集区域是否存在有指向收集区域的指针即可

卡表

卡表是记忆集的一种实现形式。

标记-清除算法

第一个算法闪亮登场,不过这哥们被用得比较少。

思想

它的做法是当堆中的有效内存空间(available memory)被耗尽的时候,就会停止整个程序(也被成为stop the world),然后进行两项工作,第一项则是标记,第二项则是清除

标记:标记的过程其实就是,遍历所有的GC Roots,然后将所有GC Roots可达的对象标记为存活的对象。

清除:清除的过程将遍历堆中所有的对象,将没有标记的对象全部清除掉。

标记/清除算法,就是当程序运行期间,若可以使用的内存被耗尽的时候,GC线程就会被触发并将程序暂停,随后将依旧存活的对象标记一遍,最终再将堆中所有没被标记的对象全部清除掉,接下来便让程序恢复运行

实例流程

image.png

这张图代表的是程序运行期间所有对象的状态,它们的标志位全部是0(也就是未标记,以下默认0就是未标记,1为已标记),假设这会儿有效内存空间耗尽了,JVM将会停止应用程序的运行并开启GC线程,然后开始进行标记工作,按照根搜索算法,标记完以后,对象的状态如下图。

https://images0.cnblogs.com/blog/558323/201308/19222543-1da7fb7bc5d24fd18872a267b65e939e.jpg

可以看到,按照根搜索算法,所有从root对象可达的对象就被标记为了存活的对象,此时已经完成了第一阶段标记。接下来,就要执行第二阶段清除了,那么清除完以后,剩下的对象以及对象的状态如下图所示。

https://images0.cnblogs.com/blog/558323/201308/19222820-8eca1c1518754d8fac6da532b13f746b.jpg

可以看到,没有被标记的对象将会回收清除掉,而被标记的对象将会留下,并且会将标记位重新归0。接下来就不用说了,唤醒停止的程序线程,让程序继续运行即可。

缺点

1、执行效率不稳定:当Java堆中有大量的对象,且其中大部分是需要被回收的;导致标记和清除两个过程的执行效率都随着对象数量增长而降低。

2、会产生大量不连续的内存碎片。

标记-复制算法(复制算法)

思想

复制算法将内存划分为两个区间,在任意时间点,所有动态分配的对象都只能分配在其中一个区间(称为活动区间),而另外一个区间(称为空闲区间)则是空闲的

当有效内存空间耗尽时,JVM将暂停程序运行,开启复制算法GC线程。接下来GC线程会将活动区间内的存活对象,全部复制到空闲区间,且严格按照内存地址依次排列,与此同时,GC线程将更新存活对象的内存引用地址指向新的内存地址

此时,空闲区间已经与活动区间交换,而垃圾对象现在已经全部留在了原来的活动区间,也就是现在的空闲区间。事实上,在活动区间转换为空间区间的同时,垃圾对象已经被一次性全部回收。

实例流程

image.png

当复制算法的GC线程处理之后,两个区域会变成什么样子,如下所示。

https://images0.cnblogs.com/blog/558323/201308/20004628-0ff08e1b8cd04e448703b01f0461e266.jpg

可以看到,1和4号对象被清除了,而2、3、5、6号对象则是规则的排列在刚才的空闲区间,也就是现在的活动区间之内。此时左半部分已经变成了空闲区间,不难想象,在下一次GC之后,左边将会再次变成活动区间。

优点

不会产生内存碎片

缺点

  • 浪费了一半的内存

  • 复制这一工作所花费的时间,在对象存活率比较高时,将会变的不可忽视

标记-整理算法

标记-清除算法与标记-整理算法的本质差异在于前者是一种非移动式的回收算法,后者是移动式的。

标记-整理算法通常使用在老年代:因为标记-清除算法会产生内存碎片(只有CMS用了标记-清除),复制算法需要损耗一般的空间且老年代的存活对象一般比较多,需要频繁进行复制,效率不高。而比较合适的就是标记整理算法了。

思想

标记:它的第一个阶段与标记/清除算法是一模一样的,均是遍历GC Roots,然后将存活的对象标记。

整理:移动所有存活的对象,且按照内存地址次序依次排列,然后将末端内存地址以后的内存全部回收。因此,第二阶段才称为整理阶段。

它GC前后的图示与复制算法的图非常相似,只不过没有了活动区间和空闲区间的区别,而过程又与标记/清除算法非常相似

实例流程

image.png

这张图其实与标记/清楚算法一模一样,只是LZ为了方便表示内存规则的连续排列,加了一个矩形表示内存区域。倘若此时GC线程开始工作,那么紧接着开始的就是标记阶段了。此阶段与标记/清除算法的标记阶段是一样一样的,我们看标记阶段过后对象的状态,如下图。

https://images0.cnblogs.com/blog/558323/201308/20210139-2062db96ff9b49309a5fedc118689bae.jpg

没什么可解释的,接下来,便应该是整理阶段了。我们来看当整理阶段处理完以后,内存的布局是如何的,如下图。

https://images0.cnblogs.com/blog/558323/201308/20210241-d6498896c30f4aed94443b65c476a797.jpg

可以看到,标记的存活对象将会被整理,按照内存地址依次排列,而未被标记的内存会被清理掉。如此一来,当我们需要给新对象分配内存时,JVM只需要持有一个内存的起始地址即可,这比维护一个空闲列表显然少了许多开销。

不难看出,标记/整理算法不仅可以弥补标记/清除算法当中,内存区域分散的缺点,也消除了复制算法当中,内存减半的高额代价。

不过任何算法都会有其缺点,标记/整理算法唯一的缺点就是效率也不高,不仅要标记所有存活对象,还要整理所有存活对象的引用地址。从效率上来说,标记/整理算法要低于复制算法。

优点

  • 消除了标记-清除算法中内存区域分散的缺点

  • 消除了复制算法中内存减半的代价

缺点

  • 执行效率低于复制算法

  • 需要整理所有对象的引用地址

  • 对象移动操作必须STW。

标签:Java,标记,对象,虚拟机,---,算法,GC,引用,垃圾
From: https://www.cnblogs.com/ayu0v0/p/18534870

相关文章

  • 深入理解Java虚拟机 --- 垃圾回收器
    Serial收集器HotSpot虚拟机运行在客户端模式下的默认新生代收集器。类型:单线程串行垃圾回收器垃圾收集算法:复制算法作用区域:新生代特点:1、只会用单个线程去完成垃圾收集工作,用户线程会STW,直到收集结束。2、没有线程交互,专心做垃圾收集,获得最高的单线程收集效率。ParNew收......
  • 深入理解Java虚拟机 --- 内存分配与回收策略
    对象优先在Eden区分配大多数情况下,对象在Eden区进行分配。当Eden区没有足够的空间来进行分配时,就会触发YoungGC(MinorGC)。当触发YoungGC时,如果Survivor区不够放存活的对象,那么就会触发分配担保机制提前转移到老年代。大对象直接进入老年代大对象的问题:1、容易导致内存明......
  • 深入理解Java虚拟机 --- 类加载机制
    类的生命周期类的生命周期:加载→验证→准备→解析→初始化→使用→卸载类加载的时机关于在什么情况下需要需要开始类加载过程的第一个阶段"加载",虚拟机并没有进行强制约束,这点交给虚拟机的具体实现来自由把握。但严格规定了有且只有六种情况必须立即对类进行"初始化":(字节码......
  • 开源模型应用落地-glm模型小试-glm-4-9b-chat-tools使用(五)
    一、前言  GLM-4是智谱AI团队于2024年1月16日发布的基座大模型,旨在自动理解和规划用户的复杂指令,并能调用网页浏览器。其功能包括数据分析、图表创建、PPT生成等,支持128K的上下文窗口,使其在长文本处理和精度召回方面表现优异,且在中文对齐能力上超过GPT-4。与之前的GLM系列......
  • JavaLin第六章:JavaLin的访问管理和默认响应
    文章目录前言一、JavaLin的访问管理二、JavaLin的默认响应总结前言最近忙起来了,就没有更新了,实在不不好意思,最近会进行javalin的陆陆续续的更新,希望大家支持。在Java里面有很多框架,其中权限管理是一个非常重要的功能实现,对于每个用户设定该用户的角色,对应相应用户......
  • 复制下来就能跑:Java智能问答系统-介绍与代码实践 - 基于springboot_springai_国产大模
    本文的目的是在5分钟内能把智能问答系统的原理和实践讲明白代码可执行,复制粘贴即可,可以快速跑起来。智能问答系统简介智能问答系统是一种人工智能应用,它能够理解用户提出的问题,并通过自然语言处理技术来分析和理解问题的含义。随后,系统会在其知识库中搜索相关信息,以生成......
  • 庖丁解java(一篇文章学java)
    (大家不用收藏这篇文章,因为这篇文章会经常更新,也就是删除后重发) 一篇文章学java,这是我滴一个执念...当然,真一篇文章就写完java基础,java架构,java业务实现,java业务扩展,根本不可能.所以,这篇文章,就是一个索引,索什么呢?  请看下文...关于决定开始写博文的介绍......
  • 【补档】玄武550电源怎么样? - 约呼的回答 - 知乎
    【补档】玄武550电源怎么样?-约呼的回答-知乎约呼一个路过的图吧用户低预算整机非常推荐,高预算慎选。低预算整机非常推荐,高预算慎选。优点不少,缺点也有。先说优点:价格上来说便宜大碗,用料不错,部分型号反向虚标,比如550v4其实是按650w规格用料来做的,拆解视频一堆,相对透明,售......
  • MM--项目中遇到的一些问题记录
    采购订单:创建采购订单时,供应商对应的信息记录已失效,但是切换供应商之后价格不会自动更新成01.SU01-查看用户对应的采购缺省值  2.SPRO-维护采购缺省值 采购订单收货的时候,提示无符合条件的行项目经打断点发现是由于程序中判断时公司间的内部交易所导致的,根本原......
  • c语言入门学习这一篇就够了-知识点总结(三万字二级必看)
    C语言   C语言是中高级语言的代表之一,它是所有现代编程语言的基石,包括C++、Java、C#、Python、JavaScript、Swift等。C语言是学习其他编程语言的基础,因为它提供了对系统底层的精确控制,这使得它在开发操作系统、驱动程序、嵌入式系统、高性能计算等领域中有着不可替代的......