• 引用计数:每次引用计数一下,没有引用就可以释放(需要添加额外的逻辑来处理循环依赖引用)→Python

• 可达性分析:从ROOT对象开始搜索,搜不到的就都可以回收→Java

  • 引用计数法 (Reference Counting): 给对象添加引用计数器。缺点: 无法解决循环引用问题,Java 不使用,但是Python使用的是这种方法。因此在后面出现循环依赖等等现象的时候Python花费了更多的思路去解决这种问题
  • 可达性分析法 (Reachability Analysis): Java 采用的方法。从 GC Roots 开始向下搜索,如果一个对象到 GC Roots 没有任何引用链相连,则认为此对象不可达,可以回收

哪些可以作为 GC Roots?

GC Root 就是那些"当前一定活着、不可能被回收"的起点引用

  • 虚拟机栈中引用的对象(局部变量)
    • 这个方法正在执行中,栈帧还在调用栈上,因此相关的变量不可以回收否则就影响栈里面运行的方法了
  • 方法区中类静态属性、常量引用的对象
    • 静态变量的生命周期跟绑定,只要类没被卸载,静态变量就一直存在,类卸载的条件极其苛刻(类加载器被回收等),绝大多数情况下类不会被卸载,所以静态变量引用的对象在程序运行期间几乎永远可达
    • 常量(static final)一旦赋值就永远不会改变。跟静态变量类似,生命周期跟类绑定,常量引用的对象在类存活期间一定不能被回收
  • 本地方法栈中 JNI 引用的对象
    • JNI 是 Java 调用 C/C++ 的桥梁,本地代码正在执行中。因为JVM 看不到 C 代码内部的逻辑,不知道本地代码什么时候会用这个对象。所以只要 JNI 持有引用,JVM 就必须假设它在被使用,不能回收
  • 所有被同步锁(synchronized)持有的对象
    • 锁对象正在被线程持有,用于同步控制。如果锁对象被回收了,其他等待这个锁的线程就永远无法被唤醒,整个同步机制崩溃,所以 JVM 必须保证锁对象在同步块执行期间存活
为什么堆里的普通对象不是 GC Root?
class Node {
    Node next;
}
// 两个对象互相引用,但没有任何 GC Root 能到达它们

因为堆里的对象可能只是互相引用(循环引用),但没有任何"活着的线程/类/锁"在用它们。如果让堆对象当 Root,循环引用就永远回收不了。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐