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

相关推荐
import_random1 小时前
[macos]rocketmq(安装)
后端
程序员小假1 小时前
你会不会使用 SpringBoot 整合 Flowable 快速实现工作流呢?
java·后端
明月与玄武2 小时前
快速掌握Django框架设计思想(图解版)
后端·python·django
陪我一起学编程2 小时前
关于ORM增删改查的总结——跨表
数据库·后端·python·django·restful
南囝coding2 小时前
这个 361K Star 的项目,一定要收藏!
前端·后端·github
虎鲸不是鱼2 小时前
Spring Boot3流式访问Dify聊天助手接口
java·spring boot·后端·大模型·llm
onlooker66662 小时前
Go语言底层(五): 深入浅出Go语言的ants协程池
开发语言·后端·golang
武子康2 小时前
Java-46 深入浅出 Tomcat 核心架构 Catalina 容器全解析 启动流程 线程机制
java·开发语言·spring boot·后端·spring·架构·tomcat
寻月隐君3 小时前
Solana 开发实战:Rust 客户端调用链上程序全流程
后端·rust·web3
丘山子4 小时前
别再滥用 None 了!这才是 Python 处理缺失值的好方法
后端·python·面试