JVM对象已死-可达性分析

JVM通过可达性分析算法 判断对象存活:从GC Roots(如栈局部变量、静态属性)出发遍历引用链。不可达对象根据引用类型(强、软、弱、虚)及finalize()复活机制判定回收,确保内存高效利用。

1. 可达性分析算法(Reachability Analysis)

  • 原理 :从一系列称为GC Roots的根对象出发,向下遍历引用链。若某个对象无法通过任何引用链连接到GC Roots,则判定为不可达,即已死。

  • GC Roots包括

    • 虚拟机栈中局部变量引用的对象(如当前执行方法中的参数、局部变量)。
    • 方法区中类静态属性引用的对象(如类的静态变量)。
    • 方法区中常量引用的对象(如字符串常量池中的引用)。
    • 本地方法栈中JNI(Java Native Interface)引用的对象。
    • 虚拟机内部引用(如基本数据类型对应的Class对象、异常对象等)。

    1. 虚拟机栈中的局部变量引用的对象

    typescript 复制代码
    public class Example {
        public static void main(String[] args) {
            // 局部变量 obj1 是 GC Root
            Object obj1 = new Object();  
            method();
        }
    ​
        static void method() {
            // 局部变量 obj2 是 GC Root(当前方法栈帧的局部变量)
            Object obj2 = new Object();  
        }
    }
    • 分析

      • obj1obj2 是虚拟机栈中的局部变量,属于 GC Roots。
      • method() 执行完毕,obj2 对应的栈帧销毁,obj2 不再是 GC Root。
      • obj1main 方法未结束时,仍是 GC Root,其引用的对象存活。

    2. 方法区中类静态属性引用的对象

    typescript 复制代码
    public class Example {
        // 静态变量是 GC Root(属于方法区)
        private static Object staticObj = new Object();  
    ​
        public static void main(String[] args) {
            Object localObj = new Object();
            staticObj = localObj;  // staticObj 指向堆中的新对象
        }
    }
    • 分析

      • staticObj 是静态变量,属于 GC Root。
      • 即使 main 方法结束,staticObj 引用的对象依然存活(除非手动置为 null)。

    3. 方法区中常量引用的对象

    arduino 复制代码
    public class Example {
        // 常量池中的字符串是 GC Root(属于方法区)
        private static final String CONSTANT_STR = "Hello";  
    }
    • 分析

      • 字符串 "Hello" 在常量池中,被 CONSTANT_STR 引用,属于 GC Root。
      • 即使没有其他引用,"Hello" 也不会被回收(除非类被卸载)。

    4. 本地方法栈中 JNI 引用的对象

    csharp 复制代码
    public class Example {
        // JNI 本地方法引用的对象是 GC Root
        public native void nativeMethod();  
    }
    • 分析

      • 通过 JNI 调用的本地方法(如 C/C++ 代码)中创建的对象,会被 JVM 特殊标记为 GC Root,防止被回收。

2. 引用类型的影响

Java提供四种引用类型,影响对象回收策略:

  • 强引用(Strong Reference) :最常见的引用,只要存在强引用,对象不会被回收。
  • 软引用(Soft Reference) :内存不足时,垃圾回收器会回收软引用对象。
  • 弱引用(Weak Reference) :无论内存是否充足,垃圾回收时都会回收弱引用对象。
  • 虚引用(Phantom Reference) :无法通过虚引用访问对象,仅用于跟踪对象被回收的状态。

3. finalize()方法的最后机会

  • 第一次标记 :若对象不可达,且未覆盖finalize()方法,或finalize()已被调用过,则直接回收。
  • 第二次标记 :若对象覆盖了finalize()方法且未被调用过,JVM会将其加入Finalizer队列,由低优先级的Finalizer线程执行该方法。若对象在finalize()中重新被引用(如赋值给静态变量),则复活并移除回收队列;否则被二次标记后回收。

示例:对象复活

java 复制代码
public class ResurrectionDemo {
    private static ResurrectionDemo instance;
​
    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        instance = this;  // 在 finalize() 中将对象重新引用
    }
​
    public static void main(String[] args) {
        instance = new ResurrectionDemo();
        instance = null;
        System.gc();
​
        // 等待 Finalizer 线程执行 finalize()
        try { Thread.sleep(500); } catch (InterruptedException e) {}
​
        System.out.println(instance);  // 输出非 null(对象被复活)
    }
}
  • 分析

    • 对象第一次被标记为可回收,但 finalize() 中将其重新赋值给 instance(静态变量,属于 GC Root)。
    • 对象复活,不会被回收。

4. 回收的最终判定

  • 对象经过可达性分析后不可达,且未通过finalize()复活,则被标记为可回收。
  • 垃圾收集器根据具体算法(如标记-清除、复制、标记-整理等)回收内存。

5.可达性分析的具体过程

示例代码

typescript 复制代码
public class ReachabilityDemo {
    public static Object staticRoot;  // GC Root(静态变量)
​
    public static void main(String[] args) {
        Object localRoot = new Object();  // GC Root(局部变量)
​
        Object objA = new Object();
        Object objB = new Object();
​
        // 构建引用链
        staticRoot = objA;      // staticRoot -> objA
        objA = objB;            // objA -> objB
        objB = localRoot;       // objB -> localRoot
        localRoot = null;       // 断开 localRoot 的引用
    }
}

可达性分析步骤

  1. 确定 GC Roots

    • staticRoot(静态变量)
    • localRoot(局部变量,但已被置为 null,不再作为 GC Root)
  2. 遍历引用链

    • staticRoot 出发:

      • staticRootobjAobjBlocalRoot(但 localRoot 已为 null,链断裂)。
  3. 标记存活对象

    • staticRootobjAobjB 通过 staticRoot 的引用链可达,因此存活。
    • localRoot 被置为 null,其原引用的对象不可达,被标记为可回收。

总结

  1. GC Roots 的类型

    • 栈中的局部变量、静态变量、常量、JNI 引用等。
  2. 可达性分析流程

    • 从 GC Roots 出发,遍历所有引用链,标记存活对象。
    • 未被标记的对象判定为可回收。
  3. 引用类型的影响

    • 强引用必须手动断开,软引用在内存不足时回收,弱引用和虚引用会被快速回收。
  4. finalize() 的注意事项

    • 只能调用一次,不保证及时执行,避免依赖它释放资源。

关键点:对象存活与否取决于是否存在一条从 GC Roots 到该对象的引用链。通过理解 GC Roots 和可达性分析,可以避免内存泄漏并优化内存使用。

相关推荐
程序员爱钓鱼3 分钟前
第一个 Rust 程序 Hello World
后端·rust
卷福同学6 小时前
不用服务器,不用配环境,我10分钟上线了一个AI Agent
人工智能·后端·算法
GetcharZp9 小时前
只需 10 分钟,轻松实现异地组网!Netmaker 保姆级部署教程来了
后端
前端兰博9 小时前
03-Spring Boot
后端
前端兰博9 小时前
01-Java开发基础语言
后端
前端兰博9 小时前
02-Servlet、JDBC、Maven、Mybatis
后端
咖啡八杯10 小时前
GoF设计模式——模板方法模式
java·后端·spring·设计模式
码事漫谈11 小时前
Loop 正在排斥人为操作
后端
爱勇宝11 小时前
办公资料反复修改、补传、交接混乱,我做了个桌面工具来解决这件事
前端·后端·程序员
庄周de蝴蝶12 小时前
梅开二度,这次拿下系统分析师
后端·程序员