垃圾回收器
并行和并发
并行(Parallel):指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。
并发(Concurrent):指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),用户程序在继续运行。而垃圾收集程序运行在另一个CPU上。
并发标记存在的对象消失问题
扫描过程中插入了一条或多条从已扫描对象到未扫描对象的新引用,并且同时去掉了正在扫描的对象到未扫描的直接引用或者直接引用,造成对象丢失。有两种方案可以解决这个问题:
1.增量更新,CMS采用了这种方案,在并发标记完成后,对新增的引用重新标记。
2.原始快照,G1收集器采取的方案,根据原始快照有变动的部分重新扫描
Minor GC 和 Full GC
- 新生代GC(Minor GC):指发生在新生代的垃圾收集动作,因为Java对象大多都具备朝生夕灭的特性,所以Minor GC非常频繁,一般回收速度也比较快。
- 老年代GC(Major GC / Full GC):指发生在老年代的GC,出现了Major GC,经常会伴随至少一次的Minor GC(但非绝对的,在Parallel Scavenge收集器的收集策略里就有直接进行Major GC的策略选择过程)。Major GC的速度一般会比Minor GC慢10倍以上。
1. serial(单线程、复制算法)
jdk.1.3.1之前唯一的新生代回收算法,只会使用一个线程去完成,并且在进行垃圾收集的同时,必须暂停其他工作线程,直到垃圾收集结束。
2. parNew(serial的多线程版本)
parNew垃圾收集器其实是serial的多线程版本,也是使用复制算法,默认开启和CPU数目相同的线程数,STW,并行回收垃圾
3. parallel scavenge(PS 多线程复制算法)
该收集器关注的是使程序达到一个可控制的吞吐量(吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集收集时间))的并行垃圾回收器,回收期间也需要STW,高吞吐量可以高效地利用CPU时间,尽快完成程序运算任务。可以通过参数指定停顿时间和垃圾收集时间占总时间的比率,也可以采用自适应调节策略,由jvm自己控制。
假设虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%。
4.serial old(单线程标记整理算法)
serial old是serial的老年代版本,也是单线程垃圾回收器,采用标记整理算法。在server模式下,主要有两个用途:
- ①. 在jdk1.5之前与新生代的parallel scavenge收集器搭配使用
-
②.作为老年代使用cms收集器的后备垃圾收集方案
serial搭配serial old
serial搭配parnew/parallel scavenge
5.parallel old收集器(多线程标记整理算法)
parallel old是parallel scavenge的老年代版本,使用多线程的标记整理算法,垃圾回收期间暂停所有用户线程,在jdk1.6才开始使用。parallel scavenge在1.6之前只能搭配serial old使用仅能保证年轻代的高吞吐量,无法保证整体。parallel old正是为了保证年老代提供同样吞吐量优先的垃圾收集器。
6.CMS收集器(多线程标记清理算法)
concurrent mark sweep是老年代的垃圾收集算法,主要目标是获取最短垃圾回收停顿时间,和其他老年代使用标记-整理算法不同,它使用多线程的标记-清除算法。最短的垃圾收集停顿时间可以为交互较高的程序提高用户体验。并发收集、低停顿算法。整个过程分为四个阶段:
1.初始标记
只是标记GC roots能直接关联的对象,速度快,STW
2.并发标记
进行GC roots跟踪的过程,和用户线程一起工作,不需要暂停工作线程
3.重新标记
为了修正并发标记期间,因用户程序继续运行而导致变动的那一部分的标记记录,STW
4.并发清除
清除GC rootss 不可达对象,和用户线程一起工作。由于耗时最长的并发标记和并发清除过程,垃圾收集器可以和用户线程一起工作,所以整体上来看,CMS收集器的内存回收过程和用户线程是一起并发执行的
CMS缺点:
- 对CPU资源敏感:默认分配的垃圾收集线程为(CPU数+3)/4,随着CPU资源下降,占用CPU资源越多,吞吐量越小
- 无法处理浮动垃圾:在并发清理阶段,由于用户线程还在运行,还会不断产生垃圾,CMS收集器无法在本次收集中清除这部分垃圾。同时由于垃圾收集阶段用户线程也在并发执行,CMS不能像其他收集器一样,等老年代填满再进行回收,需要预留出一部分空间给用户线程使用。当CSM运行时,预留的空间无法满足用户线程的需要,会出现“concurrent mode failure”的错误。这时CMS停止工作,启动后备方案,临时使用serial old 重新进行老年代垃圾回收。
- 因为CMS是基于标记清除算法,所以回收之后会存在空间碎片。可以通过参数设置在full GC之前进行一次压缩,默认开启。也可以设置在执行多少次full GC之后进行一次压缩。
G1收集器
Garbage-first是一款面向服务端的收集器,它为多处理器和大内存服务器而设计。G1收集器尝试在在满足高吞吐量需求的同时尽可能的缩短停顿时间。G1面向堆内存任何部分来组成回收集(collection Set简称 CSet),衡量标准不再是它属于哪个分代,而是哪块内存中存放的垃圾数量最多,回收效益最大,这就是G1收集器的mixed GC模式。
G1不再使用固定大小和固定数量的分代区域划分,而是把连续的堆内存划分为多个大小相等的独立区域(region),每个region都可以根据需要,扮演新生代的Eden空间、survivor空间或者老年代区域。还有一个大的内存区域,叫做humongous,主存储大对象,如果超过1.5个region,就放到humongous。
G1收集器的处理思路是跟踪各个Region里面的垃圾堆积的价值大小,在后台维护一个优先级列表,每次根据用户设定允许的收集停顿时间,优先处理回收价值收益最大的那些region。这种使用region划分空间的方法以及优先级列表,保证了G1收集器在有限的时间内能尽可能高的获取收集效率。
1.初始标记
仅仅标记和GC roots能直接关联的对象,STW
2.并发标记
从GC roots开始对堆中对象进行可达性分析,递归扫描整个堆里面的对象,耗时较长和用户线程并发执行
3.最终标记
用户处理并发标记后有变动的STAB记录,修正并发标记阶段因为用户线程继续运行而导致记录产生变动的记录,STW
4.筛选标记
负责更新Region的统计数据,堆各个region的回收价值和成本进行排序,根据用户指定的停顿时间指定回收计划,STW
G1除了并发标记外,其余阶段也是要完全暂停用户线程的,换言之,它并非纯粹地追求低延迟,官方给它设定的目标是在延迟可控的情况下获得尽可能高的吞吐量,所以才能担当起“全功能收集器”的重任与期望。
特点
- 并行与并发:在G1运行期间可以有多个GC线程同时工作,有效利用多核计算能力,此时STW;G1部分工作可以和应用程序同时执行;
- 分代收集:G1区分年轻代和老年代,年轻代依然有Eden和survivor区,但从堆得结构上看,不要求分区连续;划分为独立的region,逻辑上形成老年代和年轻代;收集器兼顾老年代和年轻代
3.空间整合:CMS采用标记-清除算法,在若干GC后进行一次碎片整理;G1回收内存以region为基本单位,region之间采用复制算法,但整体上实际看作是标记-整理算法,这两种算法都可以避免内存碎片,有利于程序长时间运行。尤其是当Java堆非常大的时候,G1优势更加明显。
4.可预测的停顿时间模型:G1相对于CMS的另一大优势,G1除了追求低停顿,还能建立可预测的停顿模型;可以根据设置的时间,回收部分区域;内部维护优先级列表,优先回收价值最大的region;相比于CMS,G1未必能够做到CMS在最好情况下的延时停顿,但是最差的情况要好很多;
G1回收器的适用场景
- 面向服务端,具有大内存、多处理器。(小内存并不优秀)
- 最主要的应用是需要低GC,并具有大堆的应用程序
- 在堆内存大小约6GB+时,可预测的暂停时间可以低于0.5秒
- 用来替换掉CMS收集器
①超过50%的Java堆活动数据占用
②对象分配频率或年代频率变化很大
③GC停顿时间超过0.5秒 - Hotspot垃圾收集器中,除了G1,其他收集器均是使用内置的JVM线程执行GC操作,而G1可以在JVM的GC线程处理速度慢时,系统调用应用程序帮助加速垃圾回收过程。