标记-清除算法
算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。
缺点
- 效率低
- 空间碎片多
复制算法
为了解决效率问题,“复制”算法出现了。他将可用的内存按容量划分为大小相等的两块。每次只使用一块。当一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已经使用过的内存空间一次清理掉。这样使得每次都对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要一动堆顶指针,按顺序分配内存即可。
缺点
- 代价高:将内存缩小为原来的一半
- 效率可能会低:在对象存活率较高时就要进行较多的复制操作
标记-整理算法
- 复制收集算法在对象存活率较高时就要进行较多的复制操作,效率会变低
- 如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对对被使用的内存中所有对象都1005存活的极端情况,所以在老年代一般不能直接选用这种算法
根据老年代的特点特出的一种算法,标记过程仍然与“标记-清除”算法一样,但后续不走不是直接对可回收对象回收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存
分代收集算法
当前商业虚拟机的垃圾收集都采用“分代收集“算法,这种算法没有什么心都思想,只是根据各个年代的特点采用最适当的收集算法。
在新生代中,每次收集都会有大量对象死去,所以可以选择复制算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择”标记-清除“或”标记整理“算法进行垃圾收集
HotSpot的算法实现
在HotSpot虚拟机上实现垃圾收集算法必须对算法的执行效率有严格的考量,才能保证虚拟机高效运行
枚举根节点
可作为GC Roots的节点主要在全局性的引用(例如常量或静态属性)与执行上下文(例如栈帧中的本地变量表)中,现在很多应用仅仅方法区就有数百兆,如果要租个检查这里的引用,那么必然会消耗很多时间
-
一致性与stop the world
可达性分析对执行时间的敏感还体现在GC停顿上,因为这项分析工作必须在一个能确保一直性的快照中进行–这里的“一致性”的意思是指在整个分析期间整个执行系统看起来就像被冻结在某个时间点上,不可以出现分析过程中对象引用关系还砸不断变化的情况,该点不满足的话分析结果准确性就无法得到保证。这点事导致GC进行时必须停顿所有Java执行线程(Stop The World)的其中一个重要原因,即使是在号称(几乎)不会发生挺短的CMS收集器中,枚举根节点也是必须要停顿的。 -
OopMap :直接知道哪些地方存放着对象引用。
在类加载完成的时候,HotSpot就把对象内什么偏移量上是什么类型的数据计算出来,在JIT编译过程中,也会在特定的位置记录下栈和寄存器中哪些位置的引用。这样,GC在扫描时就可以直接得知这些信息了。
安全点
在OopMap的协助下,HotSpot可以快速且准确滴完成GC Roots枚举,但一个很现实的问题:可能导致引用关系变化,或者说OopMap内容变化的指令非常多,如果为每一条指令都生成对应的OopMap,那将会需要大量的额外空间,这样GC的空间成本将会变得很高。
实际上,HotSpot也的确没有为每条指令都生成OopMap。只是在“特定的位置”记录了这些信息,这些位置称为安全点(Safepoint):程序执行时并非在所有地方都能停顿下来开始GC,只有在到达安全点时才能暂停。Safepoint的选定既不能太少以致于让GC等待时间太长,也不能过于频繁以致于过分增大运行时的负荷。所以安全点的选定基本上是以程序”是否具有让程序长时间执行的特征“为标准进行选定的,因为每条指令执行的时间都非常短暂,程序不太可能因为指令流长度这个原因而过长时间运行,”长时间执行“最明显特征就是指令序列复用,例如方法调用、循环跳转、异常跳转等,所以具有这些功能的指令才会产生Safepoint
抢先式中断和主动式中断
如何在GC发生时让所有线程(不包括执行JNI调用的线程)都“跑”到最近的安全点上在停顿下来?
抢先式中断:不需要线程的执行代码主动去配合,在GC发生时,首先把所有线程全部中断,如果发现有线程中断的地方不再安全点上,就恢复线程,让它“跑”到安全点上。现在几乎没有虚拟机实现采用这个方式来暂停线程从而响应GC事件。
主动式中断:当GC需要中断线程的时候,不直接对线程操作,仅仅简单地设置一个标志,各个线程执行时主动去轮训这个标志,发现中断标志为真时就自己中断挂起来。轮询标志的地方和安全点时重合的,另外在加上创建对象需要分配内存的地方。
安全区域(Safe Region,被扩展了的Safepoint)
Safepoint机制保证了程序执行时,在不太长的时间内就会遇到可进入GC的Safepoint。但是,程序“不执行”(及没有分配CPU时间)的时候无法响应JVM的中断请求,JVM也不可能等待线程重新被分配CPU时间。对于这种情况,就需要安全区域(Safe Region)来解决。
安全区域是指在一段代码片段之中,引用关系不会发生变化。在这个区域中的任意地方开始GC都是安全的。
在线程执行到Sage Region中的代码时,首先标识自己已经进入Safe Region,那样,当在这段时间JVM要发起GC时,就不用管标识自己为Safe Region状态的线程了。在线程要离开Safe Region时,它要检查系统是否已经完成了根节点枚举(或者是整个GC过程),如果完成了,那线程就继续执行,否则它就必须等待知道收到可以安全离开Safe Region的信号为止。