《深入Java虚拟机》之对象的GC

Q1. 在堆上的对象什么时候被回收

当对象不被任何变量引用的时候

Q2. 怎么样检测对象不被引用

引用计数法

给对象添加一个引用计数器,每当有一个地方引用它时,计数器就加1;当引用器为0时,该对象就变为不可用。
JVM用的不是引用计数法,证明方法就是 循环引用 (注意将GC Roots 设置为null,GC Roots是什么参考下文),然后主动调用System.gc()

可达性分析算法

主流的商用程序语言的主流实现中,都是通过可达性分析来判定对象是否存活的。这个算法的基本思想就是通过一系列的称为 GC Roots 的对象作为起始点,从这些结点开始向下搜索,搜索所走过的路径称为 引用链,当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。

红色区域的对象就会被视为 可回收对象

能作为GC Roots的对象有以下几个:

  1. 局部变量表中引用的对象
  2. 方法区中类静态属性引用的对象
  3. 方法区中常量引用的对象
  4. JNI引用的对象

Q3. 引用的定义

在JDK1.2前,引用的定义:如果reference类型的数据中存储的数值代表的是另外一块内存的起始地址,就称这块内存代表着一个引用。这种定义下,只有引用或没被引用两种情况。对于一些“食之无味,弃之可惜”的对象,作用就比较单薄。所以后来为了 当内存空间还足够时,则能保存在内存之中;如果内存空间在进行垃圾收集后还是非常紧张,则可以抛弃对象 这一理念,JDK1.2后,对引用的概念进行了扩充,将引用分为 强引用软引用弱引用虚引用 四种。

  • 强引用:和普通的赋值A a = new A() 一样,只有不可达之后才会被回收
  • 软引用:在内存溢出前,回收所有的软引用引用的对象,如果此时仍然溢出,再抛出OOM异常
  • 弱引用:发生GC时,不管内存是否足够,回收弱引用引用的对象
  • 虚引用:无法通过虚引用获取对象,只会在发生GC时,将虚引用放入Queue中(一定要传入queue)

这里说明下ReferenceQueue的作用,对象被回收后,我们需要对引用对象(即SoftReference、WeakReference等)进行处理,这就是ReferenceQueue的作用。这个类的很多属性都是由JVM进行控制的,比如ReferenceQueue.discoveredReferenceQueue.pending等属性

Q4. 无引用的对象是如何一步步被回收的

第一次GC发现不可达对象时,先判断其“有没有必要执行finalize()方法”,有必要执行时给该对象打上一个标记,并放入一个称为F-Queue的队列,等待执行finalize()F-Queue不保证每个对象的finalize()都执行完毕(因为如果finalize里有死循环之类的就凉了),过段时间,GC会对F-Queue进行第二次标记,如果想拯救里面的对象,只要将对象和GC Roots链相连即可。

没有必要执行的判断依据如下:

  • 对象没有覆盖finalize()
  • finalize()已经被虚拟机调用过了

Q5. 方法区的回收

方法区回收的效率一般比较低,方法区回收的主要对象是:废弃常量和无用的类。
比如一个字符串“abc”进入了常量池,但是当前没有任何一个String对象引用该常量,如果此时发生内存回收,如果必要的话,这个“abc”常量会被清除出常量池。其他类(接口)、方法、字段的符号引用也与此类似。
满足以下三个条件算是“无用的类”:

  1. 该类的所有实例都被回收
  2. 加载该类的ClassLoader被回收
  3. 该类的Class对象没有在任何地方被引用或使用

-verbose:class 查看类的加载信息
-XX:+TraceClassLoading 查看类的加载信息
-XX:+TraceClassUnLoading 查看类的卸载信息
-Xnoclassgc 关闭虚拟机对类的回收