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 小时前
go中锁的入门到进阶使用
开发语言·后端·golang
草捏子8 小时前
从CPU原理看:为什么你的代码会让CPU"原地爆炸"?
后端·cpu
嘟嘟MD8 小时前
程序员副业 | 2025年3月复盘
后端·创业
胡图蛋.8 小时前
Spring Boot 支持哪些日志框架?推荐和默认的日志框架是哪个?
java·spring boot·后端
无责任此方_修行中8 小时前
关于 Node.js 原生支持 TypeScript 的总结
后端·typescript·node.js
吃海鲜的骆驼9 小时前
SpringBoot详细教程(持续更新中...)
java·spring boot·后端
迷雾骑士9 小时前
SpringBoot中WebMvcConfigurer注册多个拦截器(addInterceptors)时的顺序问题(二)
java·spring boot·后端·interceptor
uhakadotcom10 小时前
Thrift2: HBase 多语言访问的利器
后端·面试·github
Asthenia041210 小时前
Java 类加载规则深度解析:从双亲委派到 JDBC 与 Tomcat 的突破
后端
方圆想当图灵10 小时前
从 Java 到 Go:面向对象的巨人与云原生的轻骑兵
后端·代码规范