上一篇我们讲了垃圾标记的一些实现细节和经典算法,而本文将系统的讲解一下垃圾回收的经典算法,和 Hotspot 虚拟机执行垃圾回收的一些实现细节,比如安全点和安全区域等。
因为各个平台的虚拟机操作内存的方法各不相同,且牵扯大量的程序实现细节,所以本文不会过多的讨论算法的具体实现,只会介绍几种算法思想及发展过程。
垃圾回收算法
1、标记 - 清除算法
标记 - 清除算法是最基础的算法,像它的名字一样算法分为“标记”和“清除”两个阶段,首先需要标记出所需要回收的对象,标记完成后统一收集被标记的对象。
优点:实现简单。缺点:产生不连续的内存碎片;“标记”和“清除”的执行效率都不高。
标记 - 清除算法执行过程图:
(本文图片来自《深入理解 Java 虚拟机》)
2、复制算法
复制算法就是将内存分为大小相同的两块,当这一块使用完了,就把当前存活的对象复制到另一块,然后一次性清空当前区块。
优点:执行效率高。
缺点:空间利用率低,因为复制算法每次只能使用一半的内存。
3、标记 - 整理算法
也称标记 - 压缩算法,标记 - 整理算法采用和标记清除算法一样的对象“标记”,但后续不会对可回收对象进行清理,而是将存活的对象往一端空闲空间移动,然后清理边界以外的内存空间。
优点:解决了内存碎片问题,比复制算法空间利用率高。
缺点:因为有局部对象移动,相对效率不高。
标记 - 整理算法执行过程图:
4、分代收集算法
目前商用虚拟机都采用的是分代收集的算法,这种算法按照对象存活周期把内存分为几块,一般 Java 中分为新生代和老年代。把存活率低的对象分到新生代使用复制算法提高垃圾回收的性能,老年代则存放存活率搞的对象,使用标记 - 清除和标记 - 整理的算法,提高内存空间使用率。
新生代和老生代的具体介绍和参数配置,后续的文章会详细讲解。
垃圾回收执行细节
本节将详细的介绍一下 HotSpot 虚拟机在执行垃圾回收时的一些细节,目的是让读者更好的理解 Java 虚拟机。
HotSpot 虚拟机:它是 Sun JDK 和 OpenJDK 自定的虚拟机,也是目前使用最广泛的虚拟机。
垃圾回收流程:Java 虚拟机在内存回收之前,为了保证内存的一致性,必须先暂停程序的执行,也就是传说中的 Stop The World(简称 STW),在使用可达性分析算法枚举 GC Roots,标记出死亡对象,再进行垃圾回收。
垃圾回收遇到的问题:那既然是要暂停程序的运行,就一定要保证停止的时间足够短,并且可控,不然带来的灾难将是毁灭性的。
解决方案:显然 HotSpot 在设计的时候也考虑到了这个问题,所以在 JIT 编译的时候就会使用 OopMap 数据结构来记录栈和寄存器上的引用,这样虚拟机就直接知道了那些地方存放着对象的引用,如下图,为我编译 String.hashCode()方法的部分本地代码:
可以看出,使用 OopMap 数据结构存储了普通对象的指针引用。
查看汇编的方法,启动命令窗体执行:java -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly YouTestClass
命令可能会报错:Could not load hsdis-amd64.dll; library not loadable; PrintAssembly is disabled
报错解决方法:使用编译好的 hsdis.dll 放到:jre 安装目录 binserver 目录下即可,hsdis.dll 地址地址:https://pan.baidu.com/s/1-D6u…
安全点(Safepoint)
在 OopMap 的协助下,HotSpot 可以快速的完成 GC Roots 枚举,但导致 OopMap 内容变化的指令很多,而且如果给每个对象生成对应的 OopMap,会造成大量额外的空间,这会导致 GC 成本很高,所以 HotSpot 只会在“特定的位置”生成对应的 OopMap,这些位置就成为“安全点”。
HotSpot 也并不是任何时刻都会停顿下来进行 GC,只会在程序都到底安全点之后才会 GC,所以安全点的设置不能太少,让 GC 等待时间太长,也不能太多增大运行时的成本。
安全点的两种线程中断方式
抢断式中断:不需要线程的执行代码去主动配合,当发生 GC 时,先强制中断所有线程,然后如果发现某些线程未处于安全点,恢复程序运行,直到进入安全点为止。
主动式中断:不强制中断线程,只是简单地设置一个中断标记,各个线程在执行时轮询这个标记,一旦发现标记被改变 (出现中断标记) 时,那么将运行到安全点后自己中断挂起。目前所有商用虚拟机全部采用主动式中断。
安全区域(Saferegion)
安全点机制仅仅是保证了程序执行时不需要太长时间就可以进入一个安全点进行 GC 动作,但是当特殊情况时,比如线程休眠、线程阻塞等状态的情况下,显然 HotSpot 不可能一直等待被阻塞或休眠的线程正常唤醒执行;此时就引入了安全区的概念。
安全区(Saferegion):安全区域是指在一段区域内,对象引用关系等不会发生变化,在此区域内任意位置开始 GC 都是安全的;线程运行时,首先标记自己进入了安全区,然后在这段区域内,如果线程发生了阻塞、休眠等操作,HotSpot 发起 GC 时将忽略这些处于安全区的线程。当线程再次被唤醒时,首先他会检查是否完成了 GC Roots 枚举(或这个 GC 过程),如果完成了就继续执行,否则将继续等待直到收到可以安全离开的 Safe Region 的信号为止。
参考
《深入理解 Java 虚拟机》
《垃圾回收的算法与实现》
最后
关注公众号,发送“gc”关键字,领取《垃圾回收的算法与实现》学习资料。