垃圾收集器在对堆进行回收前,第一件事情就是要确定这些对象之中哪些还“存活”着,哪些已经“死去”
一、对象存活判断
1.1 引用计数算法(Reference Counting)
工作原理
- 给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加 1;当引用失效时,计数器值就减 1;任何时候计数器为 0 的对象是不可能再被使用的。
分析 Java虚拟机 是否采用该算法
- 代码
/**
* JVM args :-XX:+PrintGCDetails 通过此命令可以打印GC信息
* 引用计数GC:
*/
public class ReferenceCountingGC {
private Object instance=null;
public static final int _1M=1024*1024;
private byte[] bytes=new byte[2*_1M];
public static void testGC(){
ReferenceCountingGC rA = new ReferenceCountingGC();
ReferenceCountingGC rB = new ReferenceCountingGC();
rA.instance = rB;
rB.instance = rA;
rA=null;
rB=null;
//手动发生GC
System.gc();
}
public static void main(String[] args) {
testGC();
}
}
- 输出
上面例子 rA 与 rB 互相依赖,从结果来看,内存大小从 7462k -> 784k,虚拟机进行了回收,证明虚拟机不是通过引用计数法来判断对象存活的。
1.2 可达性分析算法(Reachability Analysis)
工作原理
- 通过一系列的成为“GC Roots” 的对象作为起点,从这些节点开始向下搜索,搜索走过的路径成为引用链(Reference Chain),当一个对象到 GC Roots 没有任何 GC 引用链连接时(从 GC Roots 到这个对象不可达)则证明这个对象是不可活的。
图示:
GC Roots 的对象包括下面几种
- 虚拟机栈(栈帧中的本地变量)中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中 JNI(Natice 方法)引用的对象
二、Java 四种引用
经过上面描述得知,对象的存活都与“引用”有关。在 JDK1.2 之后,Java 对引用的概念进行了扩充,将引用分为强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Week Reference)、虚引用(Phantom Reference),引用强度依次减弱。
强引用 > 软引用 > 弱引用 > 虚引用
2.1强引用
是使用最普遍的引用,类似“Object obj = new Object()”。只要强引用还存在,垃圾收集器就不会回收被引用的对象
2.2软引用
描述一些还有用但并非必须的对象。在系统将要发生内存溢出之前,将会把这些对象列进回收范围之中进行二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。
2.3弱引用
描述非必须对象,强度比软引用还弱一些。被弱引用关联的对象只能生存到下次垃圾收集之前。当垃圾收集器工作时,无论当内存是否足够,都会回收掉只被弱引用关联的对象。
2.4虚引用
一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来获得一个对象实例。为一个对象设置虚引用关联的唯一目的就是在这个对象被收集回收时收到一个系统通知。
2.5总结
引用类型 | GC 时 JVM 内存充足 | GC 时 JVM 内存不足 | 应用场景 |
---|---|---|---|
强引用 | 不被回收 | 不被回收 | |
弱引用 | 被回收 | 被回收 | |
软引用 | 不被回收 | 被回收 | 软引用通常用于实现内存敏感缓存 |
虚引用 | 被回收 | 被回收 | 大多被用于引用销毁前的处理工作 |
参考
- 《深入理解 Java 虚拟机:JVM 高级特性与最佳实践(第 3 版)》