• 搜索
    搜新闻
  • 您的位置: 首页 >  资讯

    Java GC基础知识_全球聚焦

    博客园来源:2023-04-13 19:10:45

    1 对象存活判断

    1.1 引用计数

    在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加一;当引用失效时,计数器值就减一;任何时刻计数器为零的对象就是不可 能再被使用的


    (相关资料图)

    引用计数法的缺陷:

    public class ReferenceCountingGC {    public Object instance = null;    private static final int _1MB = 1024 * 1024;    /**    * 这个成员属性的唯一意义就是占点内存,以便能在GC日志中看清楚是否有回收过    */    private byte[] bigSize = new byte[2 * _1MB];    public static void testGC() {        ReferenceCountingGC objA = new ReferenceCountingGC();        ReferenceCountingGC objB = new ReferenceCountingGC();        objA.instance = objB;        objB.instance = objA;        objA = null;        objB = null;        // 假设在这行发生GC,objA和objB是否能被回收?        System.gc();    }}

    如果使用引用计数法objAobjB除互相引用外没有任何其他引用,但是无法被回收。

    1.2 可达性分析

    通过一些了GC Roots的根对象作为起点集,根据引用关系向下搜索,搜索过程所走过的路径称为引用链,如果某个对象和GC Roots之间没有任何引用链,则该对象不可达,需要被回收。

    GC Roots 对象

    • 虚拟机栈(局部变量表)中引用的对象
    • 方法区中静态属性引用的对象
    • 方法区在常量引用的对象
    • 本地方法栈中Native方法引用的对象
    • Java虚拟机内部的引用
    • 被同步锁持有的对象
    • 等等

    Java 引用

    引用类型描述
    强引用Object obj = new Object(); 垃圾回收器永远不会回收强引用对象
    软引用SoftReference; 系统发生内存溢出异常前,会回收软引用对象
    弱引用WeakReference; 无论内存是否足够,垃圾回收时都会回收弱引用对象
    虚引用PhantomReference; 该对象设置虚引用关联,唯一目的是该对象被回收时会收到一个系统通知

    1.3 并发可达性分析

    可达性分析在并发环境下存在的问题

    问题1: 原本消亡的对象错误标记为存活对象(可容忍) 问题2: 原本存活的对象错误标记为消亡对象(不可容忍,导致程序出错)

    可达性分析过程 —— 三色标记

    • 黑色节点: 对象已经被垃圾收集器访问过,且该对象的所有引用均已扫描
    • 灰色节点: 对象已经被垃圾收集器访问过,但该对象至少还有一个引用未被扫描
    • 白色节点: 对象尚未被垃圾收集器访问,可达性分析结束后,节点仍然为白色,则标识该对象不可达

    问题2出现的原因:同时满足以下两个条件

    • 并发标记期间程序增加一条或多条黑色对象到白色对象的引用
    • 并发标记期间删除了所有灰色对象到该白色对象的直接或间接引用,所以该对象无法被垃圾收集器访问到,导致误标记为消亡对象

    因为黑色对象已经不会再被访问,所以新增的引用无法生效;同时删除了所有其他灰色对象到该白色对象的直接或间接引用,所以该对象无法从其他引用链访问到,导致存活对象被错误标记为消亡对象。

    解决方案

    • 增量更新: 当黑色对象插入新的指向白色对象的引用关系时,就将这个新插入的引用记录下来,等并发扫描结束之后,再将这些记录过的引用关系中的黑色对象为根,重新扫描一次。如CMS垃圾回收器。
    • 原始快照: 当灰色对象要删除指向白色对象的引用关系时,就将这个要删除的引用记录下来,在并发扫描结束之后,再将这些记录过的引用关系中的灰色对象为根,重新扫描一次。如G1垃圾回收器

    2 垃圾回收算法

    2.1 标记-清除算法

    主要分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后,统一回收掉所有被标记的对象

    存在的缺陷:

    1. 执行效率不稳定,随着标记对象数量的增长而降低
    2. 内存空间碎片化问题,标记-清除会产生大量不连续的内存碎片

    2.2 标记-复制算法

    将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。

    当某一块内存快用完了,就将存活的对象复制到另一块内存上,然后把原来的内存空间直接清理即可。

    相较于标记-清除算法的优点:

    1. 每次针对一个半区进行清理,不会产生碎片化的内存空间

    存在的缺陷:

    1. 如果内存中多数对象都是存活对象,则会产生大量的内存间复制开销
    2. 将可用内存缩小为原来的一半,空间利用率低

    现代Java虚拟机大多采用标记-复制算法回收新生代,因为新生代中只有极少数对象会存活。

    2.3 标记-整理算法

    首先标记出所有需要回收的对象;让所有存活对象向内存空间的一侧移动,最后清理掉边界以外的内存。

    存在的缺陷:

    1. 移动存活对象并更新所有引用这些对象的地方是极为负重的操作,而且这种对象移动操作必须暂停用户程序才能进行(Stop The World)

    3垃圾回收器

    3.1 CMS收集器

    Concurrent Mark Sweep(CMS) 收集器是一种以获取最短回收停顿时间(STW)为目标的收集器。该回收器基于标记-清除算法实现。

    CMS收集器工作过程

    1. 初始标记(STW): 标记 GC Roots能直接关联到的对象,速度很快
    2. 并发标记: 从 GC Roots直接关联对象开始遍历整个对象图,不需要停顿用户线程
    3. 重新标记(STW): 修正并发标记,增量更新
    4. 并发清除: 清理掉所有消亡对象

    CMS收集器由于使用并发-清除算法回收,会产生大量的内存空间碎片,可用暂时容忍;当内存空间的碎片化程度影响到对象分配时,再采用一次标记-整理算法,以获得规整的内存空间。

    3.2 G1收集器

    Grabage First(G1) 收集器开创了面向局部收集的设计思路和基于Region的内存布局形式。

    基于 Region 的堆内存布局

    • 把连续的Java堆划分为多个大小相等的独立区域(Region),每一个 Region 都可用根据需分配给新生代Eden空间、Suvivor空间或者老年代空间。收集器对不同空间的 Region 块采用不同的策略处理。

    • Region 中还有一类特殊的 Humongous 区域(被视为老年代的一部分),专门用于存储大对象。G1认为只要大小超过一个Region块容量一半的对象就是大对象。每个 Region 大小为 1M ~ 32M,对于超过了整个 Region 容量的超级大对象会被存储到N个连续的Humongous Region块中。

    • G1虽然仍然保留了新生代和老年代的概念,但新生代和老年代的空间和位置不再是固定的,是一系列Region的集合。

    G1收集器工作过程

    • 初始标记(STW): 标记 GC Roots能直接关联到的对象
    • 并发标记: 从 GC Roots直接关联对象开始遍历整个对象图,不需要停顿用户线程
    • 最终标记(STW): 处理并发标记阶段结束后仍然遗留下来的STAB(原始快照)记录
    • 筛选回收: 对各个Region块的回收价值和成本进行排序,根据期望的停顿时间指定回收计划,可用自由选择任意多个Region回收,然后将被回收的Region块中存活的对象复制到空的Region中

    参考文章

    1. 深入理解Java虚拟机第3版

    关键词:

    下一篇:
    上一篇: