哪些区域需要垃圾收集

熟悉Java内存区域文中,知道了JVM运行时数据区有堆、方法区、虚拟机栈、本地方法栈和程序计数器5个部分,后3个部分都是线程私有的,其中的数据会跟随线程死亡自动回收,所以不需要垃圾收集。而堆和方法区是线程共享的,故垃圾收集主要关注这2个部分。

学习了垃圾判定算法与4大引用后,我们知道了如何判断对象是垃圾了。

接下来,我们就开始回收堆和方法区中的无效数据。

回收堆

对于可达性分析中不可达的对象,也并不是没有存活的可能。

判定 finalize() 是否有必要执行

JVM 会判断此对象是否有必要执行 finalize() 方法,如果对象没有覆盖 finalize() 方法,或者 finalize() 方法已经被虚拟机调用过,那么视为“没有必要执行”。那么对象基本上就真的被回收了。

如果对象被判定为有必要执行 finalize() 方法,那么对象会被放入一个 F-Queue 队列中,虚拟机会以较低的优先级执行这些 finalize()方法,但不会确保所有的 finalize() 方法都会执行结束。如果 finalize() 方法出现耗时操作,虚拟机就直接停止指向该方法,将对象清除。

对象重生或死亡

如果在执行 finalize() 方法时,将 this 赋给了某一个引用,那么该对象就重生了。如果没有,那么就会被垃圾收集器清除。

任何一个对象的 finalize() 方法只会被系统自动调用一次,如果对象面临下一次回收,它的 finalize() 方法不会被再次执行,想继续在 finalize() 中自救就失效了。

回收方法区

方法区存放生命周期较长的已加载的类信息、常量、静态变量和即时编译器编译后的代码4个部分,主要回收:

  • 无用的类
  • 废弃常量

判定无用的类

判定一个类是无用的,条件比较苛刻:

  • 该类的所有对象都已被清除。
  • 加载该类的ClassLoader被回收。
  • 该类的java.lang.Class对象没有被任何地方引用,无法在任何地方通过反射访问该类的方法。 满足上面3个条件的类,虚拟机会进行回收。(通过-Xnoclassgc参数控制)

一个类被虚拟机加载进方法区,那么在堆中就会有一个代表该类的对象:java.lang.Class。这个对象在类被加载进方法区时创建,在方法区该类被卸载时清除。

在大量使用反射、动态代理、CGLib等字节码框架、动态生成JSP以及OSGI这类频繁自定义ClassLoader的场景都需要虚拟机具备类卸载的功能,以保证方法区不会溢出。

判定废弃常量

条件:只要常量池中的常量不被任何变量或对象引用,那么这些常量就会被清除掉。

比如,一个字符串 “abc” 进入了常量池,但是当前系统没有任何一个 String 对象引用常量池中的 “abc” 常量,也没有其它地方引用这个字面量,必要的话,“abc"常量会被清理出常量池。

参考资料

  • 周志明 * 《深入理解Java虚拟机》