垃圾收集器在对堆进行回收前,第一件事情就是要确定这些对象之中哪些还“存活”着,哪些已经“死去”

一、对象存活判断

1.1 引用计数算法(Reference Counting)

1.1.1 工作原理

给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时候计数器为0的对象是不可能再被使用的。

1.1.2 分析JVM 是否采用该算法
  1. /**
  2. * JVM args :-XX:+PrintGCDetails 通过此命令可以打印GC信息
  3. * 引用计数GC:
  4. */
  5. public class ReferenceCountingGC {
  6. private Object instance=null;
  7. public static final int _1M=1024*1024;
  8. private byte[] bytes=new byte[2*_1M];
  9. public static void testGC(){
  10. ReferenceCountingGC rA = new ReferenceCountingGC();
  11. ReferenceCountingGC rB = new ReferenceCountingGC();
  12. rA.instance = rB;
  13. rB.instance = rA;
  14. rA=null;
  15. rB=null;
  16. //手动发生GC
  17. System.gc();
  18. }
  19. public static void main(String[] args) {
  20. testGC();
  21. }
  22. }

image.png
上面例子rA与rB互相依赖,从结果来看,内存大小从7462k -> 784k,虚拟机进行了回收,证明虚拟机不是通过引用计数法来判断对象存活的。

1.2 可达性分析算法(Reachability Analysis)

1.2.1 工作原理

通过一系列的成为“GC Roots” 的对象作为起点,从这些节点开始向下搜索,搜索走过的路径成为引用链(Reference Chain),当一个对象到GC Roots没有任何GC引用链连接时(从GC Roots到这个对象不可达)则证明这个对象是不可活的。如图:

image.png

1.2.2 GC Roots的对象包括下面几种
  • 虚拟机栈(栈帧中的本地变量)中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中JNI(Natice方法)引用的对象

二、四种引用

经过上面描述得知,对象的存活都与“引用”有关。在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内存不足 应用场景
强引用 不被回收 不被回收
弱引用 被回收 被回收
软引用 不被回收 被回收 软引用通常用于实现内存敏感缓存
虚引用 被回收 被回收 大多被用于引用销毁前的处理工作

参考