7.4 垃圾收集器
如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。
7.4.1 Serial收集器
Serial 收集器是最基本、发展历史悠久的收集器。曾是(JDK1.3.1)之前新生代收集唯一的选择。
优点: 简单高效,拥有很高的单线程收集效率
缺点: 收集过程需要暂停所有线程
算法: 复制算法
适用范围: 新生代
应用: Client 模式下默认的新生代收集器
7.4.2 ParNew收集器
Serial收集器的多线程版本。
优点:在多CPU时,比Serial效率高。
缺点:收集过程暂停所有应用程序线程,单CPU时比Serial效率差。
算法:复制算法
适用范围:新生代
应用:运行在Server模式下的虚拟机中首选的新生代收集器
7.4.3 Parallel Scavenge收集器
Parallel Scavenge收集器 是新生代收集器,又是并行 多线程收集器,看上去和ParNew一样,Parallel Scavenge收集器更关心系统吞吐量
吞吐量=运行用户代码时间/ (运行代码时间+垃圾收集时间)
比如虚拟机运行了100分钟,垃圾收集1分钟,吞吐量=(100-1)/100=99%
吞吐量越大,意味着垃圾收集的时间段,则用户代码可以充分利用CPU资源,更快的执行程序。
应用
-XX:MaxGCPauseMillis控制最大的垃圾收集停顿时间,
-XX:GCTimeRatio直接设置吞吐量的大小。
7.4.4 Serial Old 收集器
Serial Old 收集器 是Serial 收集器的老年代版本,不同的是采用“标记-整理”算法,运行过程和Serial 收集器一样。
7.4.5 Parallel Old 收集器
Parallel Old收集器是 Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理算法”进行垃圾 回收。
关注吞吐量优先
7.4.6 CMS收集器
CMS (Concurrent Mark Sweep) 收集器是一种以获取 最短回收停顿时间 为目标的收集器。
采用的是“标记-清除算法”,分为四步
1)初始标记 CMS initial mark 标记GC Roots能关联的对象,STW->速度很快
2)并发标记 CMS concurrent mark 进行GC Roots Tracing
3)重新标记 CMS remark 修改并发标记因用户程序变动的内容,STW
4) 并发清除 CMS concurrent sweep
由于整个过程中,并发标记和并发清除,收集器线程和用户线程一起工作的。
所以说CMS收集器的内存回收过程是与用户线程一起并发执行的。
优点:并发收集、低停顿。
缺点:产生大量空间碎片、并发阶段会降低吞吐量。
7.4.7 G1收集器
特点
并行与并发
分代收集
空间整合(整体上属于“标记-整理”算法,不会导致空间碎片)
可预测的停顿(比CMS更先进的地方就是 可以指定长度M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒)
使用G1收集器时,Java堆的内存布局与其他收集器很大差别,它将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分Region(不需要连续)的集合。
G1收集器工作过程
1)初始标记(Initial Marking) 标记一下GC Roots能够关联的对象,并且修改TAMS的值,需暂停用户线程
2)并发标记(Concurrent Marking) 从GC Roots进行可达性分析,找出存活的对象,与用户线程并发执行
3)最终标记(Final Marking) 修正在并发标记阶段因为用户程序的并发执行导致变动的数据,需暂停用户线程
4)筛选回收(Live Data Counting and Evacuation) 对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间制定回收计划
7.4.8 垃圾收集器分类
串行收集器:Serial和Serial Old
只能有一个垃圾回收线程执行,用户线程暂停。适用于内存比较小的嵌入式设备
。
并行收集器[吞吐量优先]: Parallel Scanvenge 和 Parallel Old
多条垃圾收集线程并发工作,但此时用户线程处于等待状态。适用于科学计算、后台处理等交互场景
并发收集器[停顿时间优先] : CMS 和 G1
用户线程和垃圾收集线程同时执行,(但并不一定并行,可能是交替执行),垃圾收集线程在执行的时候不会停顿用户线程的运行。适用于相对时间有要求的场景,比如web
7.4.9 理解吞吐量和停顿时间
-
吞吐量-> 运行用户代码时间/(用户代码时间+垃圾收集时间)
-
停顿时间->垃圾收集器进行垃圾回收终端应用执行响应的时间
停顿时间越短就越适合需要和用户交互的程序(比如电商),良好的响应速度能提升用户的体验。
高吞吐量则可以高效地利用CPU时间,尽快地完成程序的运行任务,主要适合后台运算而不太需要交互的任务。(比如定时任务)
小结:这两个指标是评价垃圾回收器好坏的标准,其实调优也是在观察这两个变量。
7.4.10 如何选择适合的垃圾收集器
-
优先调整堆的大小,让服务器自己选择
-
如果内存小于100M,使用SerialGC
-
如果是单核,并对停顿时间没有要求,使用 SerialGC 或自选
-
如果允许停顿时间超过1秒,选择ParalleGC或自选
-
如果响应时间最重要,并且不能超过1秒,使用ConcMarkSweepGC 或G1GC
7.4.11 G1深入了解
JDK7开始使用,JDK8非常成熟,JDK 9 默认使用的垃圾收集器,适合新老生代。
判断是否需要使用G1收集器?
1)50%以上的堆被存活对象占用
2)对象分配和年龄 变化非常大
3)垃圾回收时间比较长
7.4.12 如何开启需要的垃圾收集器
(1)串行
-XX:+UseSerialGC
-XX:+UseSerialOldGC
(2)并行(吞吐量优先):
-XX:+UseParallelGC
-XX:+UseParallelOldGC
(3)并发收集器(响应时间优先)
-XX:+UseConcMarkSweepGC
-XX:+UseG1GC
G1调优
选项/默认值 | 说明 |
---|---|
-XX:+UseG1GC | 使用 G1 (Garbage First) 垃圾收集器 |
-XX:MaxGCPauseMillis=n | 设置最大GC停顿时间(GC pause time)指标(target). 这是一个软性指标(soft goal), JVM 会尽量去达成这个目标. |
-XX:InitiatingHeapOccupancyPercent=n | 启动并发GC周期时的堆内存占用百分比. G1之类的垃圾收集器用它来触发并发GC周期,基于整个堆的使用率,而不只是某一代内存的使用比. 值为 0 则表示“一直执行GC循环”. 默认值为 45. |
-XX:NewRatio=n | 新生代与老生代(new/old generation)的大小比例(Ratio). 默认值为 2. |
-XX:SurvivorRatio=n | eden/survivor 空间大小的比例(Ratio). 默认值为 8. |
-XX:MaxTenuringThreshold=n | 提升年老代的最大临界值(tenuring threshold). 默认值为 15. |
-XX:ParallelGCThreads=n | 设置垃圾收集器在并行阶段使用的线程数,默认值随JVM运行的平台不同而不同. |
-XX:ConcGCThreads=n | 并发垃圾收集器使用的线程数量. 默认值随JVM运行的平台不同而不同. |
-XX:G1ReservePercent=n | 设置堆内存保留为假天花板的总量,以降低提升失败的可能性. 默认值是 10. |
-XX:G1HeapRegionSize=n | 使用G1时Java堆会被分为大小统一的的区(region)。此参数可以指定每个heap区的大小. 默认值将根据 heap size 算出最优解. 最小值为 1Mb, 最大值为 32M |
When I let go of what I am , I become what I might be.
走出舒适圈,遇见更好的自己。