垃圾回收
一、垃圾回收算法
1、标记-清除算法(Mark-Sweep算法)
1)最基础的收集算法,老年代收集算法:之所以说它是最基础的收集算法,是因为后续的收集算法都是基于这种思路并对其不足进行改进的。
2)算法分为“标记”和“清除”两个阶段:
- 标记:首先标记出所有需要回收的对象
- 清除:在标记完成后统一回收所有被标记的对象。
3)两个不足:
- 效率问题,标记和清除两个过程的效率都不高。
- 空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
2、复制算法(Copying算法)
1)算法思想: 将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。
对于复制算法的过程可以参考上一篇博客中的新生代垃圾回收过程,博客链接:JVM之垃圾回收 II.
2)复制算法是新生代的收集算法
3)优缺点:
- 优点:实现简单,运行高效,不存在内存碎片问题。
- 缺陷:内存使用率不高——50%
3、标记-整理算法(Mark-Compact算法)
1)算法分为“标记”和“整理”两个阶段:
- 标记:首先标记出所有需要回收的对象
- 整理: 让所有存活对象都向一端移动,然后直接清理掉端边界以外的内存。
2)老年代收集算法
提高了内存的利用率,适合在手机对象存活时间较长的老年代。
4、分代收集算法(Generatinal Collection)
当前JVM垃圾收集都采用的是"分代收集(Generational Collection)"算法,这个算法并没有新思想,只是根据对象存活周期的不同将Java堆分为新生代和老年代。
新生代划分:
新生代中98%的对象都是"朝生夕死"的,所以并不需要按照复制算法所要求1 : 1的比例来划分内存空间,而是将内存(新生代内存)分为一块较大的Eden(伊甸园)空间和两块较小的Survivor空间,每次使用Eden和其中一块Survivor(两个Survivor区域一个称为From区,另一个称为To区域)。HotSpot默认Eden与Survivor的大小比例是8 : 1,也就是说Eden :Survivor From : Survivor To = 8 : 1 : 1。所以每次新生代可用内存空间为整个新生代容量的90%,只有10%的内存会被”浪费“。
- 在新生代中,每次垃圾回收都有大批对象死去,只有少量存活,因此我们采用复制算法;
- 在老年代中,对象存活率高、没有额外空间对它进行分配担保,就必须采用 "标记-清理"或者"标记-整理"算法。
二、垃圾回收器
在垃圾收集器的学习中,有两个概念需要先说明:
- 并行(Parallel) : 指多条垃圾收集线程并行工作,用户线程仍处于等待状态。
- 并发(Concurrent) :指用户线程与垃圾收集线程同时执行(不一定并行,可能会交替执行),用户程序继续运行,而垃圾收集程序在另外一个CPU上。
历史的发展原因:垃圾回收算法也在发展,所以会有改进的垃圾回收器。HotSpot虚拟机提供了7种作用于不同分代的收集器,分别是:
- Serial收集器(新生代收集器,串行GC)
- ParNew收集器(新生代收集器,并行GC)
- Parallel Scavenge收集器(新生代收集器,并行GC)
- Serial Old收集器(老年代收集器,串行GC)
- Parallel Old收集器(老年代收集器,并行GC)
- CMS收集器(老年代收集器,并发GC)
- G1收集器(全区域<堆>的垃圾回收器)
在上述垃圾收集器中,前三个为新生代垃圾收集器,再紧接三个是老年代垃圾收集器,最后一个是Full GC的垃圾收集器。我们接着一一介绍。
1、Serial收集器
1)特性:
- 单线程
- 复制算法
- Stop The World(STW)
2)应用场景:Client模式下的默认新生代收集器。
原因: 在用户的桌面应用场景中,分配给虚拟机管理的内存一般来说不会很大,收集几十兆甚至一两百兆的新生代(仅仅是新生代使用的内存,桌面应用基本上不会再大了),停顿时间完全可以控制在几十毫秒最多一百多毫秒以内,只要不是频繁发生,这点停顿是可以接受的。所以,Serial收集器对于运行在Client模式下的虚拟机来说是一个很好的选择。
3)优势:
单个CPU的环境(单线程)来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程收集效率。
2、ParNew收集器
ParNew收集器是Serial收集器的多线程版本!!! 它相当于Serial收集器除了使用多条线程进行垃圾收集之外,其余行为包括Serial收集器可用的所有控制参数、收集算法、Stop The World、对象分配规则、回收策略等都与Serial收集器完全一样,在实现上,这两种收集器也共用了相当多的代码。
1)特性:
- 多线程
- 复制算法
- Stop The World(STW)
2)适用场景:
- 搭配CMS收集器,在用户体验优先(用户需要调用程序接口,然后执行程序代码)的程序中使用。
- Server模式下的虚拟机中首选的新生代收集器,其中有一个与性能无关但很重要的原因是,除了Serial收集器外,目前只有它能与CMS收集器配合工作。
3)优势:
相比单CPU环境,在多线程环境下,它对于GC时系统资源的有效利用还是很有好处的。
3、Parallel Scavenge收集器
1)特性:
-
多线程
-
复制算法
-
自适应的调节策略:
- Parallel Scavenge收集器有一个参数------>XX:+UseAdaptiveSizePolicy。
JVM设置这个参数=true之后,JVM可以监控性能,并动态的设置内存相关参数〈如年龄阈值、新生代大小、Eden和S区比例)
- 动态调整这些参数以提供
最合适的停顿时间
或者最大的吞吐量
-
可控制的吞吐量(Throughput):吞吐量:在CPU中用户运行代码的时间与CPU总消耗的时间的比值。 Parallel Scavenge收集器提供了两个参数用于精确控制吞吐量,分别是:
- 控制最大垃圾收集停顿时间的-XX:MaxGCPauseMillis参数
- 直接设置吞吐量大小-XX:GCTimeRatio参数。
2)适用场景:
“吞吐量优先”收集器,适用吞吐量需求高的任务型程序。主要是后台任务型的程序(比如定时任务,不涉及用户使用的程序)
3)优势:
- 能够精确控制吞吐量
- 可以动态的设置内存相关参数,如年龄阈值、新生代大小、Eden和S区比例
三大新生代垃圾回收器对比
4、Serial Old收集器
Serial Old是Serial收集器的老年代版本!
1)特性:
- 单线程
- “标记-整理”算法
2)适用场景:
- Client模式下的虚拟机使用。
- 在Server模式下,那么它主要还有两大用途:
- 与Parallel Scavenge收集器搭配使用
作为CMS收集器的后备预案,在CMS并发失败(Concurrent Mode Failure)时使用。
3)优势:
5、Parallel Old收集器
Parallel Scavenge收集器的老年代版本!
1)特性:
- 多线程
- “标记-整理”算法
- STW
2)应用场景:
“吞吐量优先”:在注重吞吐量以及CPU资源敏感的场合,都可以优先考虑Parallel Scavenge收集器搭配Parallel Old收集器使用。
6、CMS收集器
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间
为目标的收集器。
1)特性:
- 并发收集(多线程)
- 低停顿 (STW时间短)
- “标记-清除”算法
2)原理/实现(分为四个阶段):
- 初始标记(CMS initial mark): 仅仅标记GC Roots能直接关联的对象,
速度很快,需要STW
- 并发标记(CMS concurrent mark): 此阶段是进行GC Roots Tracing(判断对象是否仍在使用中)的过程
- 重新标记(CMS remark): 此阶段是修正第二阶段(并发标记)用户线程并发执行时导致已经标记的对象(可回收)被重新引用(有GC Roots变量指向该对象) ;
需要STW
- 并发清除(CMS concurrent sweep)∶ 并发清除对象
————> 前三个阶段是:标记,第四个阶段是:清除
四个阶段中:
- 1和3阶段:
执行速度快,消耗时间少;需要STW(暂停用户线程时间少)
- 2和4阶段:
执行时间相对较慢,但是可以和用户线程并发执行;
由于整个过程中耗时最长的并发标记和并发清除过程收集器线程都可以与用户线程一起工作
, 总体来看,CMS垃圾回收的工作是和用户线程同时执行——CMS名称的由来。
3)适用场景:互联网站或者B/S系统的服务端
因为目前很大一部分的Java应用集中在互联网站或者B/S系统的服务端上,这类应用尤其重视服务的响应速度,希望系统停顿时间最短,以给用户带来较好的体验。 CMS收集器就非常符合这类应用的需求。
4)缺陷:
-
**
CMS会抢占CPU资源
(比如现在用户线程一共50个,那么抢占cpu资源就是1/50,再加上垃圾回收线程就<1/50)。- 原因: 并发阶段虽然不会导致用户线程暂停,但却需要CPU分出精力去执行多条垃圾收集线程**
- 结果:从而使得用户线程的执行速度下降。
-
内存剩余不足,
触发Major GC导致Concurrent Mode Failure
(并发失败)- 产生原因: CMS第四个阶段(并发清除阶段),用户线程并发执行又可能导致有对象进入老年代,而老年代也可能剩余空间不足,触发Major GC——Concurrent Mode Failure(并发失败)
- 解决方案: 使用Serial Old来进行垃圾回收
-
CMS的“标记-清除”算法,会导致
大量空间碎片的产生
- 标记清除算法会导致老年代空间碎片,如果进入的对象没有连续的可用空间,触发Full GC。
7、 G1收集器
G1收集器是全区域的垃圾收集器,此处的全区域指的是堆。
1)特性:
- 整体看标记整理算法,局部看复制算法(clean\copy)
- 内存划分:堆内存划分为很多region块,每个region都是动态指定为Eden区、S区、T区(老年代内存)
2)年轻代垃圾收集原理/实现:
在G1垃圾收集器中,年轻代的垃圾回收过程使用复制算法。即把Eden区和Survivor区的对象复制到新的Survivor区域。
3)老年代垃圾收集原理/实现(分为四个阶段):
- 初始标记: 老年代对象的标记和Minor GC(新生代GC)同时执行,也就是说,在G1中,你不用像在CMS那样,单独暂停应用程序的执行来运行初始标记阶段,而是在G1触发minor gc的时候一并将年老代上的初始标记阶段给做了,即:新生代GC、老年代标记同时执行。
需要STW
; - 并发标记:
- 在这个阶段G1做的事情跟CMS一样;
- 与此同时,G1同时还多做了一件事情,就是
如果在当前阶段中,发现哪些老年代中对象的存活率很小或者基本没有对象存活,那么G1就会在这个阶段将其回收掉,而不用等到后面的clean up阶段。
这也就是Garbage First(清理老年代)
- 最终标记:
- 这个阶段G1做的事情跟CMS一样
- 但是采用的算法不同,G1采用一种叫做SATB(snapshot-at-the-begining)的算法能够在Remark阶段更快的标记可达对象。
需要STW
;
- 筛选回收: 因为和CMS算法不同,采取clean up/copy和新生代类似的整理工作。可以和Minor GC(新生代GC)同时执行,
需要STW
4)适用场景: 适用于用户体验优先,但对于吞吐量优先并不会达到很好的效果