垃圾收集器
分为七种,如下:
从功能的角度分为
1、串行:Serial、Serial Old
2、吞吐量优先:Parallel Scavenge、Parallel Old
3、响应时间优先:CMS
吞吐量优先VS响应时间优先
吞吐量=运行用户代码时间/(运行用户代码时间+运行垃圾收集时间)
可以写成这个式子:
所以垃圾收集时间越短,吞吐量越大。
Serial
Serial的意思就是串行,在进行垃圾收集时,会Stop the world(咋瓦鲁多!),其实就是暂停其他用户进程。
算法:复制算法。
HotSpot运行在客户端模式下默认的新生代收集器。
ParNew
Serial收集器的多线程并行版本
是Server模式下首选的新生代收集器,因为只有它和Serial能与CMS一起工作。
-XX:ParallelGCThreads 设置GC线程数。
Parallel Scavenge
算法:复制算法。
吞吐量优先的新生代垃圾收集器,有两个控制吞吐量的参数:
控制最大垃圾收集停顿时间:-XX:MaxGCPauseMillis
设置吞吐量大小:XX:GCTimeRatio
与ParNew的最大区别在于,它有一个GC自适应调节策略,会自动调整新生代大小、Eden和Survivor区比例、晋升老年代年龄等参数,来提供最适合的停顿时间或最大吞吐量。
开启开关: XX:+UseAdaptiveSizePolicy
Serial Old
Serial收集器的老年代版本。
算法:标记-整理算法。
供客户端模式下HotSpot虚拟机使用的老年代收集器;当CMS出现Concurrent Mode Failure后会临时启用它
Parallel Old
Parallel Scavenge收集器的老年代版本,并且只能与Parallel Scavenge搭配使用,为Parallel Scavenge而生,从JDK6开始提供。
算法:标记-整理
用于吞吐量大、处理器资源稀缺的情况。
CMS(Concurrent Mark Sweep)
追求最短停顿时间。
Concurrent并发 Mark标记 sweep清除
CMS工作流程:
- 初始标记
Stop the world(STW),然后仅仅标记CG Roots能关联的对象,时间最短。
- 并发标记
进行GC Roots Tracing的过程,不需要STW,与用户进程并发执行,时间最长。
- 重新标记
修正并发标记期间因用户程序继续运作导致标记变动的对象的标记记录,需要STW。
- 并发清除
清除死亡的对象,不需要STW,与用户进程并发进行。
问题:
1、吞吐量低:
因为要与用户进程并发执行,所以垃圾回收需要的执行时间会更长,所以吞吐量低。
2、“浮动垃圾”:
在“并发清除”的阶段,因为是并发的,用户此时可能还会产生新的垃圾,所以会留出一部分预留内存存放浮动垃圾,如果这块内存不足,会出现Concurrent Mode Failure错误,这时,虚拟机会启用Serial Old替代CMS。
3、标记-清除算法:
没有整理的过程,会产生很多空间碎片,导致分配大内存时空间不足。
G1(Garbage First)*
2004年论文发布,直到2017年成为JDK9默认垃圾回收器,取代了Parallel Scavenge+Parallel Old组合。
概念
G1依旧是分代设计,但区别是它不为新生代、老年代分配规定大小区域,而是将堆分成一个个大小固定的区域,称为Region,每个Region都可以是新生代、老年代、Eden、Survivor空间。Region成为了垃圾回收的最小单元,每次回收Region的整数倍。
同时注重吞吐量(Throughput)和低延迟(low latency),默认暂停目标200ms。
参数
-XX:+UseG1GC
-XX:G1HeapRegionSize=size 设置堆内存大小
-XX:MaxGCPauseMillis=time 暂停目标时间
G1工作流程
- 初始标记
标记一下GC Roots能直接关联的对象。(需要STW)
- 并发标记
进行GC Roots Tracing(可达性分析),判断存活对象和可回收对象,然后再处理SATB记录的有引用变动的对象。(不需要STW)
- 最终标记
短暂停一下,处理并发阶段结束后遗留下来的少量SATB记录(需要STW)
- 筛选回收
统计各个Region的回收价值和成本并排序,根据用户期望的停顿时间制定回收计划,筛选任意多个Region构成回收集,然后将回收部分的存活对象复制到空Region中,再清理整个旧Region空间。(需要STW)
跨区域引用问题
当对堆进行部分内存区域回收时,会存在跨区域引用问题。可以通过记忆集(Remembered Sets)的方式解决,记忆集列出了从外部指向了本块的所有引用。
每个Rigon都维护自己的记忆集,用来记录别的Region指向自己的指针。
并发标记时收集线程与用户线程互不干扰
1、回收时改变对象引用关系:
回收时必须保证不能打破原本的对象结构图。G1收集器通过原始快照(SATB)算法实现。
2、回收时创建新对象:
G1为每个Region设计两个名为TAMS(top at mark start)的指针,把Region中部分空间划分出来用来分配并发回收过程中产生的新对象。
内存分配与回收策略
- 通常,对象优先在Eden区分配。若Eden不足:发起一次Minor GC
- 大对象直接进入老年代。
- 长期存活的对象将会进入老年代,对象头内存储了对象的分代年龄,新生代每经历一次Minor GC就增加一岁,当年龄达到-XX:MaxTenuringThreshold(默认15)时,晋升为老年代。
- 并非要求年龄达到-XX:MaxTenuringThreshold才能晋升老年代,如果Survivo空间中相同年龄所有对象大小总和大于Survivor空间的一半,那么年龄大于等于该年龄的对象可以直接进入老年代。
- 空间分配担保:如果老年代也放不下怎么办?
每次Minor GC之前检查老年代空间是否能容纳所有新生代对象。
如果不可以:首先查看虚拟机中 -XX:HandlePromotionFailure参数设置值是否允许担保失败(Handle Promotion Failure).
如果允许:检查老年代最大可用连续空间是否大于历次晋升到老年代对象的平均大小。
如果大于:尝试进行一次Minor GC。(有风险)
如果小于:进行Full GC。
如果不允许:进行一次Full GC。