JVM通过可达性分析算法 判断对象存活:从GC Roots(如栈局部变量、静态属性)出发遍历引用链。不可达对象根据引用类型(强、软、弱、虚)及finalize()
复活机制判定回收,确保内存高效利用。
1. 可达性分析算法(Reachability Analysis)
-
原理 :从一系列称为GC Roots的根对象出发,向下遍历引用链。若某个对象无法通过任何引用链连接到GC Roots,则判定为不可达,即已死。
-
GC Roots包括:
- 虚拟机栈中局部变量引用的对象(如当前执行方法中的参数、局部变量)。
- 方法区中类静态属性引用的对象(如类的静态变量)。
- 方法区中常量引用的对象(如字符串常量池中的引用)。
- 本地方法栈中JNI(Java Native Interface)引用的对象。
- 虚拟机内部引用(如基本数据类型对应的Class对象、异常对象等)。
1. 虚拟机栈中的局部变量引用的对象
typescriptpublic 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(); } }
-
分析:
obj1
和obj2
是虚拟机栈中的局部变量,属于 GC Roots。- 当
method()
执行完毕,obj2
对应的栈帧销毁,obj2
不再是 GC Root。 obj1
在main
方法未结束时,仍是 GC Root,其引用的对象存活。
2. 方法区中类静态属性引用的对象
typescriptpublic 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. 方法区中常量引用的对象
arduinopublic class Example { // 常量池中的字符串是 GC Root(属于方法区) private static final String CONSTANT_STR = "Hello"; }
-
分析:
- 字符串
"Hello"
在常量池中,被CONSTANT_STR
引用,属于 GC Root。 - 即使没有其他引用,
"Hello"
也不会被回收(除非类被卸载)。
- 字符串
4. 本地方法栈中 JNI 引用的对象
csharppublic 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 的引用
}
}
可达性分析步骤
-
确定 GC Roots:
staticRoot
(静态变量)localRoot
(局部变量,但已被置为null
,不再作为 GC Root)
-
遍历引用链:
-
从
staticRoot
出发:staticRoot
→objA
→objB
→localRoot
(但localRoot
已为null
,链断裂)。
-
-
标记存活对象:
staticRoot
、objA
、objB
通过staticRoot
的引用链可达,因此存活。localRoot
被置为null
,其原引用的对象不可达,被标记为可回收。
总结
-
GC Roots 的类型:
- 栈中的局部变量、静态变量、常量、JNI 引用等。
-
可达性分析流程:
- 从 GC Roots 出发,遍历所有引用链,标记存活对象。
- 未被标记的对象判定为可回收。
-
引用类型的影响:
- 强引用必须手动断开,软引用在内存不足时回收,弱引用和虚引用会被快速回收。
-
finalize() 的注意事项:
- 只能调用一次,不保证及时执行,避免依赖它释放资源。
关键点:对象存活与否取决于是否存在一条从 GC Roots 到该对象的引用链。通过理解 GC Roots 和可达性分析,可以避免内存泄漏并优化内存使用。