刚刚看了别人的博客,大一技术都有深度和广度,而我大一都在干啥。。。
文章目录
- 0.gc的分类
- 1.评估GC的性能指标
-
- 1.1 指标
- 1.2 不可能三角
- 2.垃圾回收器分类
-
- 2.0 并行和并发的区别
- 2.1 按线程数分
-
- 2.1.1 串行回收
- 2.1.2 并行回收
- 2.2 按工作模式分
- 2.3 按碎片处理方式分
- 2.4 按工作的内存区间分
- 3.经典垃圾收集器的关系
- 4.七款经典垃圾收集器
-
- 4.0 总结
- 4.1 Serial收集器(新生代)
-
- 运行示意图
- 特点
- 优势:简单、高效(单线程中)
- 使用命令
- 4.2 ParNew收集器(新生代)
-
- 特点
- 优势
- 运行示意图
- 搭配
- 使用命令
- 4.3 Parallel Scavenge收集器(新生代)
- 算法
-
- 特点:吞吐量优先
- 应用场景
- 使用命令
- 自适应调节策略
- 4.4 Serial Old收集器(老年代)
-
- 介绍
- 工作过程示意图
- 4.5 Parallel Old收集器(老年代)
-
- 介绍
- 搭配
- 运行示意图
- 4.6 CMS收集器
-
- 应用场景
- 垃圾回收算法
- 运作过程
- 优点
- 弊端
- 搭配
- 4.7 Garbage first收集器
-
- 目标
- 地位
- 特点
- 适用场景
- 基于Region的堆内存布局
- G1收集器需要解决的关键问题
- 运作过程
- 优点
- 缺点
- 参数设置
- 5.低延迟垃圾收集器
-
- 5.1 Shenandoah收集器
- 5.2 ZGC收集器
0.gc的分类
HotSpot虚拟机中存在三种垃圾回收现象,minor GC、major GC和full GC。对新生代进行垃圾回收叫做minor GC,对老年代进行垃圾回收叫做major GC,同时对新生代、老年代和永久代进行垃圾回收叫做full GC。许多major GC是由minor GC触发的,所以很难将这两种垃圾回收区分开。major GC和full GC通常是等价的,收集整个GC堆。
(HotSpot虚拟机在1.8之后已经取消了永久代。)
1.评估GC的性能指标
1.1 指标
1.吞吐量:运行用户代码的时间/总运行时间。总运行时间=程序的运行时间+内存回收的时间。
2.垃圾收集开销:吞吐量的补数。垃圾收集所用时间与总运行时间的比例。
3.暂停时间:执行垃圾收集时,程序的工作线程被暂停的时间。
4.收集频率:相对于应用程序的执行,收集操作发生的频率。
5.内存占用:Java堆区所占的内存大小。
6.快速:一个对象从诞生到被回收所经历的时间。
1.2 不可能三角
主要看:吞吐量、暂停时间、内存占用。
(这三者共同构成一个“不可能三角”。三者总体的表现会随着技术进步而越来越好。一款优秀的收集器通常最多同时满足其中的两项。)
这三项里,暂停时间的重要性日益凸显。因为硬件发展,内存占用和吞吐量问题能容忍。而内存的扩大,对延迟反而带来负面效果。
2.垃圾回收器分类
如图,橙色为垃圾回收线程,绿色为用户线程。
2.0 并行和并发的区别
并行:并行描述的时多条垃圾收集器线程之间的关系,说明同一时间有多条这样的线程在协同工作,通常默认此时用户线程是处于等待状态。
并发:并发描述的是垃圾收集器线程与用户线程之间的关系,说明同一时间垃圾收集器线程与用户线程都在运行。由于用户线程并未被冻结,所以程序仍能响应服务请求,但由于垃圾收集器线程占用了一部分系统资源,此时应用程序的处理的吞吐量将受到一定影响。
2.1 按线程数分
2.1.1 串行回收
定义:在同一时间段内只允许有一个CPU用于执行垃圾回收操作。此时工作线程被暂停,直至垃圾收集工作结束。
应用:在诸如单CPU处理器或者较小的应用内存等硬件平台不是特别优越的场合,串行回收器的性能表现可以超过并行回收器和并发回收器。所以,串行回收默认被应用在客户端的Client模式下的JVM中。
2.1.2 并行回收
在并发能力比较强的CPU上,并行回收器产生的停顿时间要短于串行回收器。
并行回收器可以运用多个CPU同时执行垃圾回收,因此提升了应用的吞吐量。不过并行回收仍然与串行回收一样,采用独占式,使用Stop The World。
2.2 按工作模式分
2.3 按碎片处理方式分
2.4 按工作的内存区间分
3.经典垃圾收集器的关系
4.七款经典垃圾收集器
4.0 总结
4.1 Serial收集器(新生代)
运行示意图
特点
是一个单线程工作的收集器:不仅只用一个处理器或一条收集线程去完成垃圾收集工作,更强调它进行垃圾收集时,必须暂停其他所有工作线程,直到它收集结束。(Stop the world)
优势:简单、高效(单线程中)
额外内存消耗最小;
对于单核或少核的处理器,由于Serial没有线程交互的开销,专心做垃圾收集自然获得最高的单线程手机效率。
使用命令
-XX:+UseSerialGC
4.2 ParNew收集器(新生代)
特点
是Serial收集器的多线程并行版本。(同时使用多条线程进行垃圾收集)
优势
多CPU多核,提高吞吐量,更快完成垃圾收集。
运行示意图
新生代:回收次数频繁,采用并行更高效
老年代:回收次数少,串行省去切换线程的资源
搭配
1.CMS:自JDK9开始,它俩只能互相搭配使用。
2.Serial Old:自JDK9开始,这样的组合被取消了。
使用命令
-XX:+UseParNewGC(在JDK9中被取消,意味着ParNew完全被废弃了)
默认开启的线程数与核心线程数相同,可以使用-XX:ParallelGCThreads参数来限制垃圾收集的线程数
4.3 Parallel Scavenge收集器(新生代)
算法
标记复制
特点:吞吐量优先
收集器的目标是达到一个可控制的吞吐量。
吞吐量=运行用户代码时间/(运行用户代码时间+运行垃圾收集时间)
应用场景
例如:批量处理、订单处理、工资支付、科学计算
停顿时间越短就越适合需要与用户交互或需要保证服务响应质量的程序,良好的响应速度能提高用户体验;而高吞吐量则可以最高效率地利用处理器资源,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的分析任务。
使用命令
-XX:MaxGCPauseMillis 控制最大垃圾收集停顿时间(允许1个大于0的毫秒数)
-XX:GCTimeRatio 直接设置吞吐量大小(默认99,即垃圾回收时间小于1%)
自适应调节策略
-XX:+UseAdaptiveSizePolicy
当这个参数被激活后,就不需要人工指定细节参数了。虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量。
4.4 Serial Old收集器(老年代)
介绍
Serial Old收集器是Serial Old收集器的老年代版本,它同样是一个单线程收集器。使用标记-整理算法。这个收集器的主要意义也是供客户端模式下的HotSpot虚拟机使用。
工作过程示意图
4.5 Parallel Old收集器(老年代)
介绍
Parallel Old是Parallel Scavenge收集器的老年代版本,支持多线程并发收集,基于标记整理算法实现。
搭配
JDK8默认:Parallel Old+Parallel Scavenge
运行示意图
4.6 CMS收集器
应用场景
并发低停顿收集器。
CMS是一种以获取最短回收停顿时间为目标的收集器。互联网网站或者基于浏览器B/S系统的服务端上,这类应用通常关注服务的响应速度,希望系统的停顿时间尽可能短,以给用户带来良好的交互体验。
垃圾回收算法
Concurrent Mark Sweep基于标记-清除算法实现。
为什么不用标记整理呢?为了保证用户线程能继续执行,它运行的资源不受影响。
运作过程
四个步骤:
1.初始标记。需要stop the world。时间短。初始标记仅仅标记一下GC Roots能直接关联到的对象。
2.并发标记。不需要stop the world。时间最长。并发标记从GC Roots的直接关联对象开始遍历整个对象图,这个过程耗时长但不需要停顿用户线程,可以与垃圾收集线程一起并发运行。
3.重新标记。需要stop the world。比初始标记时间长。为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录。
4.并发清除。不需要stop the world。清理删除标记阶段判断的已经死亡的对象。由于不需要移动存活对象,这个阶段也是可以与用户线程同时并发的。
优点
1.并发收集
2.低延迟
弊端
1.会产生内存碎片。导致并发清除后,用户线程可用的空间不足。在无法分配大对象的情况下,不得不提前触发Full GC。
2.CMS收集器对CPU资源非常敏感。在并发阶段,它虽然不会导致用户停顿,但是会因为占用了一部分线程而导致应用程序变慢,总吞吐量会降低。
3.CMS收集器无法处理浮动垃圾。可能出现“Concurrent Mode Failure”失败而导致另一次Full GC的产生。在并发标记阶段如果产生新的垃圾对象,CMS将无法对这些垃圾对象进行标记,最终导致新产生的这些垃圾对象没有被回收,从而只能在下一次GC时释放这些之前未被回收的内存空间。
搭配
使用CMS收集老年代的时候,新生代只能选择ParNew、Serial。
4.7 Garbage first收集器
目标
适应不断扩大的内存和不断增加的处理器数量,进一步降低暂停时间,同时兼顾良好的吞吐量。
被称为“全功能收集器”。
地位
自JDK9,成为服务端模式下的默认垃圾收集器。
特点
它可以面向堆内存任何部分来组成回收集进行回收,衡量标准不再是它属于哪个分代, 而是哪块内存中存放的垃圾数量最多,回收收益最大,这就是G1收集器的Mixed GC模式。
适用场景
针对具有大内存、多处理器的机器。
基于Region的堆内存布局
G1收集器之所以能建立可预测的停顿时间模型,是因为它将Region作为单次回收的最小单元。
把连续的Java堆划分为多个大小相等的独立区域(Region),每一个Region都可以根据需要,扮演新生代的Eden空间,Survivor空间、或者老年代空间。
Region还有一类特殊的Humongous区域,专门存储大对象。(大对象:超过一个Region容量一半的对象。每个Region的大小可以通过参数-XX:G1HeapRegionSize设定,取值范围为1MB~32MB,且应为2的N次幂)。对于那些超过了整个Region容量的超级大对象,将会被存放在N个连续的Humongous Region之中,G1的大多数行为都把Humongous Region作为老年代的一部分来进行看待。
G1收集器需要解决的关键问题
涉及的技术,见 垃圾收集算法的细节实现
1.跨Region引用问题。
每个Region都维护有自己的记忆集,这些记忆集会记录下别的Region指向自己的指针,并标记这些指针分别在哪些卡页的范围之内。G1的记忆集在存储结构的本质上是一种哈希表,Key是别的Region的起始地址,Value是一个集合,里面存储的元素是卡表的索引号。这种双向的卡表结构(记录“我指向谁、谁指向我)比原来的卡表实现起来更复杂,同时由于Region数量比其他传统垃圾收集器分代数量明显要多得多,因此G1收集器要比其他的传统垃圾收集器有着更高的内存占用负担。根据经验,G1至少要耗费大约相当于Java堆容量10%至20%的额外内存来维持收集器工作。
2.在并发标记阶段如何保证收集线程与用户线程互不干扰的运行?
G1通过原始快照(SATB)算法实现。
垃圾收集对用户线程的影响还体现在回收过程中新创建对象的内存分配上,程序要继续运行就肯定会有新对象被创建,G1为每一个Region设计了两个名为TAMS(Top at Mark Start)的指针,把Region中的一部分空间划分出来用于并发回收过程中的新对象分配,并发回收时新分配的对象地址都必须要在这两个指针位置以上。G1收集器默认它们是存活的,不纳入回收范围。
3.怎样建立起可靠的停顿预测模型?
运作过程
除了并发标记都需要stop the world。
1.初始标记:仅仅只是标记一下GC Roots能直接关联到的对象,并且修改TAMS指针的值,让下一阶段用户线程并发运行时,能正确地在可用的Region中分配新对象。这个阶段需要停顿线程,但耗时很短,而且是借用进行Minor GC的时候同步完成的,所以G1收集器在这个阶段实际并没有额外的停顿。
2.并发标记:从GC Root开始对堆中对象进行可达性分析,递归扫描整个堆里的对象图,找出要回收的对象,这阶段耗时较长,但可与用户线程并发执行。当对象图扫描完成后,还要重新处理SATB记录下的在并发时有引用变动的对象。
3.最终标记:对用户线程做另一个短暂的暂停,用于处理并发阶段结束后仍遗留下来的最后那少量的SATB记录。
4.筛选回收:负责 更新Region的统计数据,对各个Region的回收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划,可以自由选择任意多个Region构成回收集,然后把决定回收的那一部分Region的存活对象复制到空的Region中,再清理掉整个旧Region的全部空间。这里的操作涉及存活对象的移动,是必须暂停用户线程,由多条收集器线程并行完成的。
优点
1.运作期间不会产生内存空间碎片,垃圾收集完成之后能提供规整的可用内存。这种特性有利于程序长时间运行,在程序为大对象分配内存时不容易因无法找到连续内存空间而提前触发下一次收集。
2.可以由用户指定期望的停顿时间。默认停顿目标为200毫秒。
但如果把停顿时间设置过低,很可能出现的结果就是由于停顿目标时间太短,导致每次选出来的回收集只占堆内存很小的一部分,收集器收集的速度逐渐跟不上分配器分配的速度,导致垃圾慢慢堆积。很可能一开始收集器还能从空闲的堆内存中获得一些喘息的时间,但应用运行时间一长就不行了,最终占满堆引发Full GC反而降低性能,所以通常把期望停顿时间设置为一两百毫秒或者两三百毫秒会是比较合理的。
缺点
内存占用和额外执行负载比CMS更高。