IT评测·应用市场-qidao123.com技术社区

标题: 内存管理:判断对象是否存活 [打印本页]

作者: 兜兜零元    时间: 2023-4-4 14:37
标题: 内存管理:判断对象是否存活
在堆里面存放着 Java 世界中几乎所有的对象实例,垃圾收集器在对 Java 堆进行回收前,第一件事情就是要确定这些对象之中哪些还“存活”着,哪些已经“死去”(“死去”即不可能再被任何途径使用的对象)。
有两种判断对象是否存活的算法:引用计数算法、可达性分析算法。
引用计数算法

引用计数算法(Reference Counting)判断对象是否存活的基本思路是:在对象中添加一个引用计数器,每当有一个地方引用该对象时,计数器的值就加一;当引用失效时,计数器的值就减一;任何时刻计数器为零的对象就是不可能再被使用的对象。
客观地说,引用计数算法虽然占用了一些额外的内存空间来进行计数,但引用计数算法的原理简单,判定效率也很高,在大多数情况下它都是一个不错的算法。也有一些比较著名的应用案例, 例如微软 COM(Component Object Model)技术、使用 ActionScript3 的 FlashPlayer、Python 语言以及在游戏脚本领域得到许多应用的 Squirrel 中都使用了引用计数算法进行内存管理。
但是,在 Java 领域,至少主流的 Java 虚拟机里面都没有选用引用计数算法进行内存管理,主要原因是,这个看似简单的算法有很多例外情况要考虑,必须要配合大量的额外处理才能保证正确地工作,譬如单纯的引用计数就很难解决对象之间相互循环引用的问题。
举个简单的例子,请看代码清单 3-1 的 testGC() 方法:对象 objA 和 objB 都有字段 instance,赋值令 objA.instance=objB 及 objB.instance=objA,除此之外,这两个对象再无任何引用,实际上这两个对象已经不可能再被访问,但是因为它们互相引用着对方, 导致它们的引用计数都不为零,引用计数算法也就无法回收它们。
代码清单 3-1 引用计数算法的缺陷
  1. /**
  2. * testGC()方法执行后, objA和objB会不会被GC呢?
  3. *
  4. * @author zzm
  5. */
  6. public class ReferenceCountingGC {
  7.     public Object instance = null;
  8.     private static final int _1MB = 1024 * 1024;
  9.     /**
  10.      * 这个成员属性的唯一意义就是占点内存, 以便能在GC日志中看清楚是否有回收过
  11.      */
  12.     private byte[] bigSize = new byte[2 * _1MB];
  13.     public static void testGC() {
  14.         ReferenceCountingGC objA = new ReferenceCountingGC();
  15.         ReferenceCountingGC objB = new ReferenceCountingGC();
  16.         objA.instance = objB;
  17.         objB.instance = objA;
  18.         objA = null;
  19.         objB = null;
  20.                 // 假设在这行发生GC, objA和objB是否能被回收?
  21.         System.gc();
  22.     }
  23. }
复制代码
可达性分析算法

当前主流的商用程序语言(Java、C#,上溯至古老的 Lisp)的内存管理子系统,都是通过可达性分析(Reachability Analysis)算法来判断对象是否存活。
可达性分析算法判断对象是否存活的基本思路是:通过一系列被称为 “GC Roots” 的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径被称为 “引用链”(Reference Chain),如果某个对象到 GC  Roots 间没有任何引用链相连(用图论的话来说就是从 GC Roots 到这个对象不可达)时,则证明此对象是不可能再被使用的对象。
如下图所示,对象 object 5、object 6、object 7 虽然互有关联,但是它们到 GC Roots 是不可达的,因此它们将会被判定为是可回收的对象。

在 Java 技术体系里面,固定可作为 GC Roots 的对象包括以下几种:
除了这些固定的 GC Roots 集合以外,根据用户所选用的垃圾收集器以及当前回收的内存区域不同,还可以有其他对象 “临时性” 地加入,共同构成完整的 GC Roots 集合。譬如后文将会提到的分代收集和局部回收(Partial GC),如果只针对 Java 堆中某一块区域发起垃圾收集时(如最典型的只针对新生代的垃圾收集),必须考虑到内存区域是虚拟机自己的实现细节(在用户视角里任何内存区域都是不可见的),更不是孤立封闭的,所以某个区域里的对象完全有可能被位于堆中其他区域的对象所引用,这个时候就需要将这些关联区域的对象也一并加入 GC Roots 集合中去,这样才能保证可达性分析的正确性。
参考资料

《深入理解 Java 虚拟机》第 3 章:垃圾收集器与内存分配策略 3.2 对象已死?

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!




欢迎光临 IT评测·应用市场-qidao123.com技术社区 (https://dis.qidao123.com/) Powered by Discuz! X3.4