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 和可达性分析,可以避免内存泄漏并优化内存使用。

相关推荐
满分观察网友z2 分钟前
解锁 Java 的“上帝模式”:我如何用反射和注解,从“测试地狱”走向“一键自动化”
后端
无奈何杨10 分钟前
CoolGuard风控节假日完善,QLExpress自定义函数
前端·后端
这里有鱼汤19 分钟前
通过AI狂赚苹果26.6%,这套AI金融交易开源Agent彻底火了
后端·agent
寻月隐君28 分钟前
【Solana 开发实战】轻松搞定链上 IDL:从上传到获取全解析
后端·web3·github
程序员爱钓鱼39 分钟前
Go项目上线部署最佳实践:Docker容器化从入门到进阶
后端·google·go
汪子熙40 分钟前
Visual Studio Code 中排除指定文件夹搜索的最佳实践与实现原理
后端·面试
大P哥阿豪1 小时前
Go defer(二):从汇编的角度理解延迟调用的实现
开发语言·汇编·后端·golang
风象南1 小时前
SpringBoot 与 HTMX:现代 Web 开发的高效组合
java·spring boot·后端
wstcl2 小时前
让你的asp.net网站在调试模式下也能在局域网通过ip访问
后端·tcp/ip·asp.net
ai小鬼头10 小时前
Ollama+OpenWeb最新版0.42+0.3.35一键安装教程,轻松搞定AI模型部署
后端·架构·github