JVM

回顾《深入理解 Java 虚拟机》之垃圾收集器

第二篇,GC 并非 Java 的专利,事实上最早 Lisp 语言就有了 GC 和内存动态分配的概念

Posted by ChenJY on February 6, 2019 | Viewed times

GC 并非 Java 的专利,事实上最早 Lisp 语言就有了 GC 和内存动态分配的概念。GC 的重点在于两个:

  1. 如何判定对象的存活与否
  2. 如何进行垃圾回收,这里又分为不同的回收算法

如何判断对象存活

关于 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,因为回收效率低不值得,但是归根到底这是由具体虚拟机的实现去决定的。

永久代的垃圾收集主要回收两部分内容:废弃常量和无用的类。回收废弃常量的方法和回收堆中对象非常相似;但是判断一个类是否无用却较复杂,需要同时满足以下三个条件:

  1. 该类所有实例都已被回收
  2. 加载该类的 ClassLoader 已被回收
  3. 该类对应的 java.lang.Class 对象没有任何地方被引用,无法通过反射访问该类的方法

参考资料

-《深入理解Java虚拟机》 周志明著

License


这是一个不定时更新的、披着程序员外衣的文青小号。

在这里,既分享极客技术,也记录人间烟火,欢迎关注。

0

Comment