垃圾回收器详解
一、垃圾回收分类
1、串行垃圾回收
在同一时间段内只允许只有一个cpu用于垃圾回收操作,此时工作线程被暂停,直至垃圾收集工作结束
(1)在单核cpu,或者较小的应用平台上使用串行垃圾回收优于并行回收,串行回收默认应用在客户端的client模式下的jvm中
(2)在多核cpu下,并行回收器产生的停顿时间短于串行回收器
(3)独占式,会有stop-the-world
2、并行垃圾回收
(1)多个cpu同时回收,提升了应用的性能
(2)独占式,会有stop-the-world
二、GC性能指标
1、吞吐量
运行用户代码的时间占总运行时间的比例
总运行时间=程序运行时间+内存回收时间
2、垃圾收集开销
垃圾收集时间占总运行时间的比例
3、暂停时间
执行垃圾收集时,程序的工作线程被暂停的时间
4、收集频率
相对于应用程序的执行,收集操作发生的频率
5、内存占用
java堆区所占用的内存大小
6、快速
一个对象从诞生到被回收所经历的时间
总结:
吞吐量,暂停时间和内存占用这三者构成了一个“不可能三角”,三者总体的表现会随着技术的进步越来越好,一款优秀的收集器通常最多同时满足其中两项。
随着技术的发展,内存占用越来越能容忍,硬件性能提升有助于降低收集器运行对应用程序的影响,提高的吞吐量。但是内存扩大,GC暂停时间会变长。
目前主要关注吞吐量,暂停时间
高吞吐量较好会让应用程序运行越快,低延迟(低暂停时间)较好,可以有效缓解用户线程的被挂起的时间,特别是交互式应用程序对低延迟的要求很高。
高吞吐量和低暂停时间是对立的,如果选择了高吞吐量,那必然降低内存回收的频率,但是导致GC需要更长的暂停时间。
如果选择了低延迟,那么必然要频繁执行内存回收,造成吞吐量的下降。
三、垃圾回收器概述
jdk主要有7中垃圾回收器
1、串行回收器
Serial、Serial Old
2、并行回收器
ParNew、Parallel Scavenge、Parallel Old
3、并发回收器
CMS、G1
垃圾收集器与垃圾分代的关系
1、新生代收集器
Serial、ParNew、Parallel Scavenge
2、老年代收集器
Serial Old、Parallel Old、CMS
3、整堆收集器
G1
查看jdk默认垃圾收集器方式
(1)-XX:+PrintCommandLineFlags
(2)jinfo -flag 垃圾回收器参数 进程ID
四、垃圾回收器
1、Serial(串行回收)
(1)jdk1.3之前回收新生代的唯一选择
(2)Hotspot中client模式下默认的新生代收集器
(3)采用复制算法,串行回收和stw(Stop the world)机制方式执行内存回收
优点:
(1)简单高效
(2)对于桌面内存不大的应用,使用串行回收,可以接受的。
(3)使用-XX:+UseSerialGC参数可以指定年轻代和老年代都使用串行收集器,等价于新生代使用Serial,老年代使用Serial Old
2、Serial old(串行回收)
(1)老年代使用收集器,也有stw机制,使用标记-压缩算法
(2)运行在client模式下默认的老年代垃圾回收器
(3)在server端有两个用途
1)与新生代Parallel Scavenge配合使用
2)作为老年代CMS收集器的后备方案
3、ParNew(并行回收)
(1)Serial的多线程版本
(2)采用复制算法,stw机制
新生代,回收次数频繁,使用并行方式高效
老年代,回收次数少,使用串行方式节省资源,减少了线程间切换开销
使用-XX:+UseParNewGC指定使用ParNew收集器在新生代收集,使用-XX:ParallelGCThreads限制线程数量,默认开启和CPU相同的线程数。
4、Parallel Scavenge(吞吐量优先的并行收集)
(1)采用复制算法、并行回收,stw机制
(2)吞吐量优先
(3)具有自适应调节策略(ParNew是没有的)
适合在后台运算,不需要太多交互的任务,比如服务器环境中执行批量处理,科学计算等
5、Parallel Old
(1)jdk1.6引入的老年代收集器
(2)采用标记压缩,并行和stw机制
(3)jdk8默认的垃圾收集器(Parallel Scavenge+Parallel Old)
-XX:+UseParallelGC和-XX:+UseParallelOldGC这两个参数默认开启一个,另一个也会被开启。
-XX:+ParallelGCThreads设置年轻代的线程数,一般和cpu数相同,如果cpu数量小于8,则使用cpu数量;如果cpu数量大于8,则使用公式3+(5*cpu_count/8)
-XX:MaxGCPauseMillis设置垃圾收集器最大停顿时间,单位毫秒。
-XX:GCTimeRatio垃圾收集时间占总时间的比例,取值范围(0,100),默认值99,即垃圾回收时间不超过1%
-XX:+UseAdaptiveSizePolicy设置Parallel Scavenge收集器的自动适应调节策略
(1)对年轻代的大小,Eden和Survivor的比例,晋升老年代的年龄参数会自动调节,以达到堆大小,吞吐量和停顿时间的平衡。
(2)对调优困难的场景,可以使用这种自适应方式,仅设定最大堆,目标吞吐量和停顿时间参数,其他由虚拟机自己调节。
6、CMS(低延迟,并发)
(1)jdk1.5引入的CMS(Concurrent-Mark-Sweep)
(2)第一次实现了用户线程和垃圾回收线程同时运行
(3)采用标记-清除算法和stw机制,会出现内存碎片,内存分配使用空闲列表
(4)当堆内存使用达到某一阈值时,便开始进行回收,以确保应用程序在CMS工作中有足够内存运行,如果cms运行期间,预留的内存无法满足程序运行,会抛出“Concurrent Mode Failure”,这时虚拟机采用后备方案,启用Serial Old收集器,这样停顿时间会变长。
工作原理
(1)初始标记(stw)
标记出GC Roots能直接关联到的对象,一旦标记完成之后,恢复之前暂停的所有应用线程。
(2)并发标记
从GC Roots的直接对象开始遍历整个对象图的过程,这个过程较耗时,但是不需要停止用户线程。
(3)重新标记
在并发标记阶段,用户线程和回收线程同时运行,为了修正并发阶段,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录。该阶段比并发标记时间短
(4)并发清除
清除掉标记阶段中被判定死亡的对象,释放内存空间。
为什么cms不是用标记压缩?
因为并发清除时,用压缩整理内存的话,原来的线程使用的内存有可能被修改了,所以压缩整理算法更适合在stw环境下。
优点
(1)并发执行
(2)低延迟
缺点
(1)有内存碎片,经过若干次GC后再整理
(2)占用cpu资源,影响用户吞吐量
(3)无法处理浮动垃圾
在初始标记,并发标记之后产生的垃圾不能被及时回收,只能等到下一次垃圾回收
参数配置
-XX:+UseConcMarkSweepGC 手动指定该参数,将自动打开-XX:+UseParNewGC,Serial old
-XX:CMSInitiatingOccupanyFraction 设置堆内存使用率的阈值,一旦到达该阈值即触发回收
在jdk5及以前默认值是68,即老年代达到68%即回收。jdk6及以后默认值为92%
7、G1
(1)jdk7引入,jdk9默认回收器,再jdk8想启用G1,使用-XX:+UseG1GC
(2)适应不断扩大的内存和不断增加的处理器数量,进一步降低暂停时间,同时兼顾良好的吞吐量
(3)区域分代化
把内存划分为多个(约2048个)Region(物理上不连续,小大相同),整体控制在1M到32M,且为2的N次幂,使用不同的Region来表示Eden,幸存者0区,幸存者1区,老年代等,
G1有计划的避免在整个堆中进行全区域的垃圾收集,g1跟踪各个Region里面的垃圾堆积的价值大小(回收空间和回收时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值大的Region
一个Region只属于一个角色(Eden,Survivor,Old),且角色可以变,H用于存储大对象,比如查过1.5个Region时,就放入H中。如果一个H放不下,就会找连续的H区来存放,为了找连续的H,有时需要Full GC。
每个Region内存划分如下图,使用指针碰撞分配内存
优点
(1)在部分阶段可以和用户线程一起运行。
(2)回收时,不要求整个Eden区、整个年轻代和整个老年代的内存回收,而是使用若个Region的逻辑年轻代和老年代
(3)可以回收老年代和新生代
(4)Region之间是复制算法,但整体上是标记压缩算,不会有内存碎片
(5)具有可预测的停顿时间模型,能让使用者明确在一个长度为M毫秒的时间片段内,消耗在垃圾回收的时间不得超过N毫秒。
(6)在GC慢时,采用应用线程承担GC工作。
缺点
(1)在小内存上的应用CMS的表现优于G1,G1适合在大内存上发挥优势。
(2)在垃圾回收时产生的内存占用,和执行负载都比CMS要大高。
参数设置
一般而言,使用G1,基本上有三步
(1)开启G1垃圾回收器
(2)设置最大堆内存大小
(3)设置最大停顿时间
G1的使用场景
(1)面向服务端大内存,多处理器应用
(2)超过50%的java堆被活动数据占用,对象分配频率或者年代提升频率变化很大,GC停顿时间过长。
G1垃圾回收过程有三个环节
(1)年轻代GC
当年轻代Eden区用完时开始年轻代回收,回收时会暂停所有应用线程,启动多线程执行年轻代回收,将年轻代对象移入幸存者区或者老年代区
(2)老年代并发标记
当堆内存使用达到45%,开始老年代并发标记
(3)混合回收
标记完成后,就开始混合回收,即年轻代和老年代一起回收
记忆集和写屏障
总结:
如果最小化使用内存和并行化开销,使用Serial GC
如果最大化应用程序吞吐量,使用Parallel GC
如果最小化GC中断和停顿时间,使用CMS