• JVM垃圾回收相关算法


     仅以此记录学习笔记等!

    基本回收算法

    在堆中几乎保存着所有能够想到的对象实例,也就是根据对象存活的周期不同,将内存分为不同的块。

    而目前的JVM中处理垃圾收集的方式--几乎都采用的分代收集算法(即**将对象熬过垃圾收集的次数,视为对象的年龄,也依此将对象至少划分为新生代和老年代这两个代**)。

     

    标记-清除算法

    简介:最基础的收集算法是“标记-清除”(Mark-Sweep)算法,如其名,分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象;也可以返回来标记存活的对象,统一回收没有被标记的对象。后续算法都是基于这个优化改进的。

    缺点

    • 效率问题:如果需要标记的对象太多,或者清除的对象太多,效率都不高

    • 空间问题:标记清除后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后分配较大对象时,无法找到足够的连续的内存而不得不提前触发另一次垃圾收集操作。

    回收前:

    img

    回收后:

    img

     

    标记-复制算法

    简介:为了解决效率问题,“复制”收集算法出现了。它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。使得每次都是对整个半区进行内存回收,内存分配时也不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,运行高效。

    缺点:

    • 直接将原有内存缩小为了原来的一半,代价太高了。

    • 如果对象存活率较高时就要进行较多的复制操作,效率将会变低。

     

    回收前:

    img

    回收后:

    img

     

    标记-整理算法

    复制算法缺点就是对象存活率较高时,进行的复制操作多,效率低下,更关键的时浪费的50%的内存空间。如果不想浪费,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况,所以老年代一般不能直接选择复制算法。

    根据老年代的特点(对象存活率高,难于GC),出现了“标记-整理”算法。标记过程仍然与“标记-清除”算法一样,但后续步骤中不再直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。

    回收前:

    img

    回收后:

    img

    分代收集算法

    在新生代中,每次垃圾回收都会有大批对象死去,只有少数存活,那就选用复制算法,只要付出少量存活对象的复制成本就可以完成收集。

    而老年代中因为对象存活率高,没有额外空间对它进行分配担保,就必须使用“标记-清除”或者“标记-整理”算法来进行回收。

     

    HotSpot算法实现

    众所周知,一般回收内存方法包括计数法、可达性分析法。可达性分析法即GC ROOTS(枚举根结点,找到引用链)。

    以此为例,1.枚举的节点主要存在于全局性的引用(如常量)与执行上下文(如栈帧的局部变量表)中,目前很多应用仅方法区就有数百兆,如果逐一检查,必定消耗很多时间。2.另可达性分析对执行时间也很敏感,这就体现在GC的停顿上,因为分析工作必须在一个时间点上,不可以出现分析过程中,对象的引用关系还会发生变化的情况,不然无法保证准确性。这也是导致GC时必须停顿所有Java执行线程(STOP THE WORLD)的一个重要原因。

     

    随即引出安全点的概念:

    在HotSpot中,当执行系统停顿下来后,其会使用一组成为OopMap的数据结构来记录对象的引用,保存对象内什么偏移量上是什么类型的数据,以及特定位置记录下栈和寄存器中有哪些位置是引用。这样在GC扫描时,就能直接得知相关信息了。

    在OopMap的协助下,HotSpot可以快速准确地完成GC ROOTS枚举,但随着引用关系变化,或者OopMap内容变化的指令太多,如果为每一条指令都生成OopMap指令,那就会需要大量的额外空间。

    所以HotSpot也不可能为每条指令都生成OopMap。只有在特定的位置才会记录下栈、寄存器信息,这些位置称为“安全点”(SafePoint),即程序执行时并非在所有地方都停顿下来进行GC,只在安全点停顿。

    SafePoint的选定既不能太少以至于让GC等待时间太长,也不能太多导致GC过于频繁,加大运行时的负荷。

    所以,安全点的选择基本上是以程序“是否具有让程序长时间执行的特征”为标准进行选定的--因为每条指令执行的时间都非常短暂,程序不太可能因为指令流长度太长就过长时间运行。“长时间执行”的最明显的特征就是指令序列复用,例如:方法调用、循环跳转、异常跳转等(方法返回之前、调用某个方法之后、抛出异常的位置、循环的末尾)。

    安全区域:savePoint是对正在执行的线程的设定。如果一个线程处于Sleep或中断状态,它就不能响应JVM的中断请求,会卡住运行到savePoint上,因为JVM引入了safe Region。safe Region指的是一段代码片段中,引用关系不会发生变化,在这个区域内的任意地方开始GC都是安全的。

    我始终记住:青春是美丽的东西,而且对我来说,它永远是鼓舞的源泉。——(现代)巴金
  • 相关阅读:
    AtCoder Grand Contest 033
    Luogu P6620 [省选联考 2020 A 卷] 组合数问题
    Luogu P6631 [ZJOI2020] 序列
    Luogu P6630 [ZJOI2020] 传统艺能
    Luogu P6633 [ZJOI2020] 抽卡
    Luogu P6623 [省选联考 2020 A 卷] 树
    AtCoder Grand Contest 034
    Luogu P5445 [APIO2019] 路灯
    LOJ #6059. 「2017 山东一轮集训 Day1」Sum
    Luogu P3721 [AH2017/HNOI2017]单旋
  • 原文地址:https://www.cnblogs.com/flyinglion/p/14854313.html
Copyright © 2020-2023  润新知