1、垃圾回收算法
1.1、标记-清除算法(Mark-Sweep):
过程分为“标记”和“清除”两个过程。先将所有需要回收的目标统一标记,然后再统一清除。
不足:
1.“标记”和“清除”两个过程的效率并不高。
2.无法保证得到的内存是否是连续的。当存在大量的零碎的内存空间,但任一内存块均无法满足某个较大的对象存放时,还需要临时触发一次垃圾回收。
1.2、复制算法(Yong区分为8:1:1的那种算法)
这就是HotSpot中的方法:将内存区分为Eden区和两块Survivor(分别称为s0和s1)区。每次都将内存放在Eden区和s1,第一次回收之后,将还存活的对象放到s2中,保证s0和s1有一块是空的。每次均回收非空的Survivor区和Eden区,将存活的放入空余的Eden区中。配合分代使用,避免Survivor放不下存活的对象。
缺点:在对象存活率较高时,要进行较多的对象复制,影响效率。
1.3、标记-整理算法(Mark-Compact):Old区采用这种方法。
标记:同上,将所有的需要回收的对象统一标记。
整理:让存活的不需要被回收的对象,向一端移动,最后只要清除掉边界以外的对象(不需要回收的都移动到一端了)。
1.4、分代收集算法
将内存分为Yong区和Old区,然后根据不同区的特点分别对不同的区采用不同的收集算法。
2、HotSpot的算法实现
HotSpot作为sun公司的主流虚拟机,在实现上述算法时,必定非常严谨。
2.1、在判断对象是否存活时,采用“GC Roots”的遍历引用链,当可作为“GC Roots”的节点太多,从头到尾走遍引用链花费时间太长怎么办?
确实,如果GC Roots太多,或者引用链太复杂时,从头到尾走遍引用链,会让程序停顿时间太长(gc线程运行时要停止所有线程以保证gc运行时不会产生多余的引用变化)。在虚拟机中多采用准确式GC,即在编译和类加载时,HotSpot就把对象内什么偏移量上是什么数据结构计算出来,并在JIT编译时在特定的位置记录下栈和寄存器中哪些位置是引用,然后将这些信息保存到一组称为OopMap的数据结构中。
2.2、那么在什么位置生成OopMap,或者OopMap的生成条件,以防止过多的OopMap生成占用太多的内存空间。
OopMap保存的是什么:在编译之后的指令中,如果某条指令或某段指令含有对象的引用,即在此生成OopMap,指明在某个寄存器和栈的具体偏移量的内存区域存在对象的引用/引用指针。
所以,哪些位置需要生成OopMap,跟这些位置是否含有对象引用有关。需要生成OopMap的点成为“安全点”,即可以停下来执行GC的点。这些安全点的位置:不能太少,这样GC等待时间长。也不能过于频繁,会增加运行时的负荷(在运行时会在安全点调用是否执行gc的确认信息)。
2.3、如何让程序在安全点进入GC?
首先,为什么要让程序在安全点进入gc,是因为安全点保存了引用信息,程序在此位置进入GC,GC能轻松的扫描到引用信息,然后遍历引用链。
那么如何保证进入GC时,所有的线程都在安全点上呢。方法一:抢先式中断(Preemptive Suspension),在GC发生时,首先把所有线程都中断,然后让没有在安全点的线程跑到安全点上去再停下来(现在几乎没有虚拟机采用这种)。方法二:主动式中断(Voluntary Suspension),当GC需要中断线程时,在安全点设置一个轮询标志(线程走到安全点即去询问),当轮询标志为真时,线程就停下来。进行GC。
2.4、要是程序没有执行,线程不启动呢(例如sleep状态)?
除了安全点(Safepoint),还有安全区(Safe Region)。所谓安全区,就是将安全点扩展为一个区域,在这个区域,引用关系不糊发生变化。进入安全区就告诉GC,如果要出安全区,需要得到GC 执行完毕的通知。