Java 虚拟机 gc算法总结

时间:2022-12-28 08:36:36

一、垃圾收集基本的算法

1. 引用计数(Reference Counting)

为每一个对象添加一个计数器,计数器记录了对该对象的活跃引用的数量。如果计数器为0,则说明这个对象没有被任何变量所引用,即应该进行垃圾收集。
收集过程如下:
1)减少被收集对象所引用的对象的计数器的值
2)将其放入延时收集队列之中

引用计数的方法需要编译器的配合。编译器需要为此对象生成额外的代码。如赋值函数将此对象赋值给一个引用时,需要增加此对象的引用计数。还有就是,当一个引用变量的生命周期结束时,需要更新此对象的引用计数器。引用计数的方法由于存在显著的缺点,实际上并未被JVM所使用。

2. 标记-清除收集器(Mark-Swap Collectors)

收集过程分为2个阶段
1)首先停止所有工作,从根集遍历所有被引用的节点,然后进行标记,最后恢复所有工作
2)收集阶段会收集那些没有被标记的节点,然后返回空闲链表

标记-清除法的缺点在于
1)标记阶段暂停的时间可能很长,而整个堆在交换阶段又是可访问的,可能会导致被换页换出内存。
2)另外一个问题在于,不管你这个对象是不是可达的,即是不是垃圾,都要在清楚阶段被检查一遍,非常耗时.
3)标记清楚这两个动作会产生大量的内存碎片,于是当有大对象进行分配时,不需要触发一次垃圾回收动作

3. 拷贝收集器(Copying Collectors)

该算法的提出是为了克服句柄的开销和解决堆碎片的垃圾回收。将内存分为两个区域(fromspace和tospace)。所有的对象分配内存都分配到fromspace。在清理非活动对象阶段,把所有标志为活动的对象,copy到tospace,之后清楚fromspace空间。然后互换fromsapce和tospace的身份。既原先的fromspace变成tosapce,原先的tospace变成fromspace。每次清理,重复上述过程。
现在的商业虚拟机都用这种算法来回收新生代,因为新生代的大多数的生命周期都很短暂,所以前面提到的两块相互切换的区域并不需要按照1:1来进行分配。而是分配了一个Eden区,两个Survivor区。大部分对象默认的都是在Eden区中生成。当垃圾回收时,Eden和其中的一个Survivor区的存活对象将被复制到另外一个Survivor区,当另外一个Survivor区也满了的时候,从Eden和第一个Survivor区复制过来的并且此时还存活的对象,将被复制到tenured。


4. 标记-整理收集器(Mark-Compact Collectors)(适用于存放生命周期较长对象的tenured generation:PSOldGen)
标记整理收集器,通过融合了标记-清除收集器和拷贝收集器的优点,很好的解决了拷贝收集策略中,堆内存浪费严重的问题。
标记整理收集器分为2个阶段
1)标记阶段,这个阶段和标记-清除收集器的标记阶段相同
2)整理阶段,这个阶段将所有做了标记的活动对象整理到堆的底部

分代总结:生命周期较长的对象,归入到tenured generation。一般是经过多次minor gc,还 依旧存活的对象,将移入到tenured generation。(当然,在minor gc中如果存活的对象的超过survivor的容量,放不下的对象会直接移入到tenured generation)tenured generation的gc称为major gc,就是通常说的full gc。由于tenured generaion区域比较大,而且通常对象生命周期都比较长,所以这部分的gc时间比较长。minor gc可能引发full gc。当eden+from space的空间大于tenured generation区的剩余空间时,会引发full gc。这是悲观算法,要确保eden+from space的对象如果都存活,必须有足够的tenured generation空间存放这些对象。

2、Java HotSpot VM 年轻代和年老代垃圾回收算法

1. 年轻代:

1)Serial(串行的复制算法,对应GC log 为 DefNew)

2)ParNew(多线程的Serial复制算法,对应GC log 为 ParNew)

3)PS(多线程的复制算法,关注吞吐量的改善,对应GC log 为 PSYounGen)


2. 年老代:

1)Serial MSC(串行的标记清除算法,对应GC log为Tenured)

2)并行MSC(并行的标记清除算法,对应GC log为PSOldGen)

3)并行Compating(并行的标记整理算法,对应GC log为ParOldGen)

4)并发CMS(并发的标记清除算法,对应GC log为CMS)