学习内容:第3章 - 垃圾收集器与内存分配策略
可达性分析算法(Reachability Analysis)
昨天学习的内容中,引用计数法是一种原理简单、判定高效的算法,但是由于其本身的一些缺陷,使用时需要同时设计较多的逻辑判断和其他机制才能保证可靠性。所以 Java 虚拟机并没有选择使用引用计数法,而是使用了今天要学习的可达性分析算法(Reachability Analysis)来判定对象是否存活
作者指出,古老的 Lisp 语言和当前主流的商用程序语言里Java 和 C# 的内存管理,都选用了可达性分析算法来判定对象的存活。基本思路就是通过一系列称为**“GC Roots”的根对象**作为起始节点集,从这些节点开始根据引用关系向下进行搜索,搜索走过的路径称为“引用链”(Reference Chain),如果某个对象到 GC Roots 之间没有任何引用链,证明此对象是不可能再被使用的,将会被判定为可回收的对象。
在 Java 中,固定可作为 GC Roots 的对象包括以下几种:
- 在虚拟机栈(栈帧中的本地变量表)中引用的对象,比如当前正在运行的方法使用到的参数、局部变量、临时变量等
- 方法区中类静态属性引用的变量,比如 Java 类的引用类型静态变量
- 方法区中常量引用的对象,比如字符串常量池(String Table)里的引用
- 本地方法栈中 Native 方法引用的对象
- Java 虚拟机内部的引用,如基本数据类型对应的 Class 对象,一些常驻的异常对象(如
NullPointException
、OutOfMemoryError
)等,还有系统类加载器 - 所有被同步锁(
synchronized
关键字)持有的对象 - 反映 Java 虚拟机内部情况的
JMXBean
、JVMTI
中注册的回调、本地代码缓存等
除了这些固定的 GC Roots 集合以外,根据用户所选用的垃圾收集器以及当前回收的内存区域不同,还可以有其他对象“临时性加入”,共同构成完整的 GC Roots 集合。
Java 中引用的概念
无论是引用计数法还是可达性分析算法,判断对象是否存活都和“引用”离不开关系,在 JDK 1.2 之前,Java 里的引用还是很传统的定义,即:如果 reference 类型的数据中存储的数值代表的是另一块内存的起始地址,就称该 reference 数据是代表某块内存、某个对象的引用。这个定义并没有问题,但在现在看来过于狭隘了,一个对象只有“被引用”和“未被引用”的两种状态,无法描述一些“食之无味弃之可惜”的对象。我们希望对一类对象进行描述,当内存空间还足够时,能够保留在内存之中,而如果内存空间在进行垃圾收集之后仍然非常紧张,就抛弃这些对象。
因此,在 JDK 1.2 之后,Java 对引用的概念进行了扩充,将引用分为 4 类:
- 强引用(Strongly Reference)
- 软引用(Soft Reference)
- 弱引用(Weak Reference)
- 虚引用(Phantom Reference)
强引用(Strongly Reference)
强引用就是最传统引用的定义,指的是在程序代码中普遍存在的引用赋值,如 Object obj = new Object()
这种引用关系。无论任何情况下,只要强引用关系还存在,垃圾收集器就不会回收掉被引用的对象
软引用(Soft Reference)
软引用就是用来描述一些还有用,但非必须的对象。只被软引用关联的对象,在系统将要发生内存溢出异常前,会把这些对象列入到回收范围内进行第二次回收,如果这次回收后还没有足够的内存,才会抛出内存溢出异常。在 JDK 1.2 之后提供了 SoftReference
类来实现软引用
弱引用(Weak Reference)
弱引用也用来描述非必须对象,但是强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生为止。当垃圾收集器开始工作,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。JDK 1.2 提供了 WeakReference
类来实现弱引用
虚引用(Phantom Reference)
也被称为“幽灵引用”或者“幻影引用”,它是最弱的一种引用关系,一个对象是否有虚引用存在,完全不会对齐生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用的唯一目的知识为了能在这个对象被收集器回收时收到一个系统通知。JDK 1.2 之后提供了 PhantomReference
类来实现虚引用