GC
并非 Java
的专利,事实上最早 Lisp
语言就有了 GC
和内存动态分配的概念。GC
的重点在于两个:
- 如何判定对象的存活与否
- 如何进行垃圾回收,这里又分为不同的回收算法
如何判断对象存活
关于GC roots
选取的这个问题,知乎上 R大
有过详细的回答,这里贴上链接:Java 的 GC 为什么要分代?,顺便表达一下对R大的敬仰,简直是知乎清流啊,努力成为 R大
那样的人!这篇回答中涉及到了 GC root
的一些内容。其中很重要的一点是强调了 GC roots 必须是一组活跃的引用,而不是对象。
再谈引用
上述的两种判断方法都涉及到引用,但在 JDK1.2
以前,Java
中对引用的定义过于狭隘(或者说分类太单一):只要是引用类型的数据中存储的是代表另一快内存的起始地址,就称为这块内存代表着一个引用。过于简单、粗暴的定义固然理解容易,但是无法便捷地扩充概念,凡是都有轻重缓急,GC
这种代价大的操作更是如此,因此总要通过引用进行一些区分,例如区分引用的强弱、区分引用的优先级等,从而可以针对性的进行垃圾回收。
因此 JDK1.2
之后 Java
对引用的概念进行了扩充,分为四种类型,其引用强度依次减弱,详细的内容请看我之前的文章:Java里四种引用类型的作用和区别
可达性分析中的“缓刑”过程
真正宣告一个对象的死亡需要经历两次标记,第一次为搜索后未与 GC roots
引用链相连,则被标记并且此时会进行一次筛选,筛选条件是此对象是否有必要执行 finalize()
方法。
如果对象判定为需要执行,那么这个对象会先被放在一个 F-Queue
的队列中,稍后由一个虚拟机自动创建的、低优先级的 Finalizer
线程去执行,这里虚拟机只是触发这个方法,并不会等它结束,这样做是避免 F-Queue
中的其他对象等待时间过长。finalize()
方法中,只要对象能与引用链上的任何一个对象建立关联,就可以成功拯救自己,那么第二次小规模标记时就被移出待回收队列。
方法区中的 GC
方法区(或者是 HotSpot
中的永久代)确实不一定需要 GC
,因为回收效率低不值得,但是归根到底这是由具体虚拟机的实现去决定的。
永久代的垃圾收集主要回收两部分内容:废弃常量和无用的类。回收废弃常量的方法和回收堆中对象非常相似;但是判断一个类是否无用却较复杂,需要同时满足以下三个条件:
- 该类所有实例都已被回收
- 加载该类的
ClassLoader
已被回收 - 该类对应的
java.lang.Class
对象没有任何地方被引用,无法通过反射访问该类的方法
参考资料
-《深入理解Java虚拟机》 周志明著