回收堆和方法区中对象
哪些区域需要垃圾收集
在熟悉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虚拟机》