目录
6、Parallel Scavenge 吞吐量优先 (与Parallel Old搭配)
垃圾收集器:垃圾收集器是垃圾回收算法(引用计数法、标记清除法、标记整理法、复制算法)的具体实现
现在使用最多的是1.8的版本:年轻代默认是Parallel Scavenge,老年代是Parallel Old
jdk1.8内置了G1,jdk1.9的默认收集器是G1
STW (stop the world)是什么?
stop the world指的是GC事件发生过程中,会产生应用程序的停顿。停顿产生时整个应用程序线程都会被暂停,没有任何响应, 有点像卡死的感觉,这个停顿称为STW。
1、Java的垃圾收集器的分类
1)是否分代?
- 分代收集器:分代收集器顾名思义就是将Java堆中新生代和老年代使用不同的垃圾收集器来完成垃圾收集工作。
- 不分代收集器:不分新生代老年代
2)工作区域?
- 新生代收集器(全部的都是复制算法):Serial、ParNew、Parallel Scavenge
- 老年代收集器:CMS(标记-清除)、Serial Old(标记-整理)、Parallel Old(标记整理)
3)串行并行?
串行(Serial:一个GC线程一个的执行,GC执行产生STW
并行(Parallel):并行描述的是多条GC之间的关系,同一时间有多条这样的线程在协同工作,此时用户线程处于等待状态
并发(Concurrent):并发描述的是垃圾收集器线程与用户线程之间的关系,垃圾收集器线程与用户线程都在运行。由于用户线程并未被冻结,程序仍然能响应服务请求,但由于垃圾收集器线程占用了一部分系统资源,此时应用程序的处理的吞吐量将受到一定影响。
- 串行收集器Serial:Serial、Serial Old
- 并行收集器 Parallel:Parallel Scavenge、Parallel Old
- 并发收集器:CMS、G1
2、收集器的特点
3、评估GC的性能指标
1)吞吐量:运行用户代码时间/(运行用户代码时间+垃圾回收时间)
虚拟机总共运行了 100 分钟,其中垃圾收集1分钟,那吞吐量就是 99%。
2)暂停时间:执行垃圾收集时,程序的工作线程被暂停的时间。
3)收集频率:相对于应用程序的执行,收集操作发生的频率。
4)内存占用:Java堆区所占的内存大小。
吞吐量、暂停时间、内存占用 这三者共同构成一个“不可能三角”。
三者总体的表现会随着技术进步而越来越好。一款优秀的收集器通常最多同时满足其中的两项。这三项里,暂停时间的重要性日益凸显。因为随着硬件发展,内存占用多些越来越能容忍,硬件性能的提升也有助于降低收集器运行时对应用程序的影响,即提高了吞吐量。
抓住两点:
- 注重吞吐量:Parallel Scavenge
- 注重暂停时间:CMS、G1 低延迟
4、Serial收集器
特点:单线程、简单高效(与其他收集器的单线程相比),对于限定单个CPU的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程手机效率。收集器进行垃圾回收时,必须暂停其他所有的工作线程,直到它结束(Stop The World)。
5、ParNew收集器
Par是Parallel的缩写,New:只能处理的是新生代
ParNew收集器其实就是Serial收集器的多线程版本,除了使用多线程外其余行为均和Serial收集器一模一样。
与串行收集器搭配使用:
- 对于新生代,回收次数频繁,使用并行方式高效。
- 对于老年代,回收次数少,使用串行方式节省资源。(CPU并行需要切换线程,串行可以省去切换线程的资源)
6、Parallel Scavenge 吞吐量优先 (与Parallel Old搭配)
- Parallel Scavenge 收集器的目标则是达到一个可控的吞吐量,通过设置参数可以精准控制吞吐量,故也称为吞吐量优先收集器。
- 在程序吞吐量优先的应用场景中,Parallel Scavenge收集器和Parallel old收集器的组合,在server模式下的内存回收性能很不错。在Java8中,默认是此垃圾收集器。
- 高吞吐量则可以高效率地利用CPU时间,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务。因此,常见在服务器环境中使用。例如,那些执行批量处理、科学计算、订单处理、工资支付的应用程序。
7、CMS收集器
面试:CMS和G1的区别?
1. 为什么出现CMS?
有了多线程Parallel,还是有STW,想注重用户线程的响应时间,不想让用户线程STW太多,所以就引入了CMS(Concurrent Mark Sweep),从名字看使用垃圾清除算法。
2. 应用场景?
互联网站或者B/S系统的服务端,这类应用尤其重视服务的响应速度,希望系统停顿时间最短,以给用户带来较好的体验。CMS收集器就非常符合这类应用的需求。
3. 工作流程
(CMS的原理)
CMS整个过程比之前的收集器要复杂整个过程分为4个主要阶段,即初始标记阶段、并发标记阶段、重新标记阶段和并发清除阶段。(涉及STW的阶段主要是:初始标记 和 重新标记)
- 初始标记阶段(STW):所有用户线程暂停,标记出GC Roots能直接关联的对象,由于直接关联对象比较小,速度非常快。
- 并发标记阶段(耗时):从GC Roots直接关联对象开始遍历整个对象图的过程,可以和用户线程并发执行,耗时,不会STW
- 重新标记阶段(STW):所有用户线程暂停,修正并发标记期间,因用户线程继续运作而导致标记产生变动部分对象的标记记录
- 并发清除阶段(耗时):清理标记阶段判断为死亡的对象,释放空间内存,不需要移动存活对象,可以和用户线程并发执行
总结:
1、CMS收集器采用的是并发回收(非独占式),但是在其初始化标记和再次标记这两个阶段中仍然需要执行“Stop-the-World”机制暂停程序中的工作线程,不过暂停时间并不会太长,因此可以说明目前所有的垃圾收集器都做不到完全不需要“stop-the-World”,只是尽可能地缩短暂停时间。
2、最耗费时间的并发标记与并发清除阶段都不需要暂停工作,所以整体的回收是低停顿的。
4. 问题
1)CMS使用时要保证在并发执行用户线程有足够的内存使用(了解)
由于在垃圾收集阶段用户线程没有中断,所以在CMS回收过程中,还应该确保应用程序用户线程有足够的内存可用。因此,CMS收集器不能像其他收集器那样等到老年代几乎完全被填满了再进行收集,而是当堆内存使用率达到某一阈值时,便开始进行回收,以确保应用程序在CMS工作过程中依然有足够的空间支持应用程序运行。要是CMS运行期间预留的内存无法满足程序需要,就会出现一次“Concurrent Mode Failure” 失败,这时虚拟机将启动后备预案:临时启用Serial old收集器来重新进行老年代的垃圾收集,这样停顿时间就很长了,Serial old使用的是标记-整理算法,正好可以对CMS标记-清除产生的碎片进行整理。
2)有了重新标记,为什么还要浮动垃圾的问题?
并发标记阶段用户线程可能产生两种变动:
- new出来的新对象,要用的,GCroot还不可达(重新标记处理:让gc指向成为可达)
- 有一些对象不用了,原来gc root可达,后来不可达(浮动垃圾)
重新标记解决的时第一种情况
浮动垃圾是第二种情况
相比之下,浮动垃圾是可容忍的问题,那为什么重新标记阶段不处理第一种变动呢?由于从可达变为不可达的变化需要重新从GC Roots开始遍历,相当于再次完成初始标记和并发标记的工作,这样会造成增加重新标记阶段的开销,所带来的暂停时间是追求低延迟的CMS不能容忍的。
3)CMS为什么不用标记整理算法?
答案其实很简答,因为当并发清除的时候,用标记整理算法整理内存的话,原来的用户线程使用的内存还怎么用呢?要保证用户线程能继续执行,前提的它运行的资源不受影响嘛。Mark Compact更适合“stop the world” 这种场景下使用
5. 优缺点
- 优点:
-
- 并发收集
- 低延迟
- 缺点:
-
- 会产生内存碎片,导致并发清除后,用户线程可用的空间不足。在无法分配大对象的情况下,不得不提前触发FullGC。
- CMS收集器对CPU资源非常敏感。在并发阶段,它虽然不会导致用户停顿,但是会因为占用了一部分线程而导致应用程序变慢,总吞吐量会降低。
- CMS收集器无法处理浮动垃圾。在并发标记阶段由于程序的工作线程和垃圾收集线程是同时运行或者交叉运行的,那么在并发标记阶段如果产生新的垃圾对象,CMS将无法对这些垃圾对象进行标记,最终会导致这些新产生的垃圾对象没有被及时回收,从而只能在下一次执行GC时释放这些之前未被回收的内存空间。
8、G1收集器
1. 为什么需要G1(Garbage First)
应用程序所应对的业务越来越庞大、复杂,用户越来越多。
官方给G1设定的目标是在延迟可控的情况下获得尽可能高的吞吐量,所以才担当起“全功能收集器”的重任与期望。
2. G1的特点
1) 并行与并发
G1能充分利用多CPU、多核环境下的硬件优势,使用多个CPU来缩短Stop-The-World停顿时间。部分收集器原本需要停顿Java线程来执行GC动作,G1收集器仍然可以通过并发的方式让Java程序继续运行。
2)分代收集 ♥
- 从分代上看,G1依然它会区分年轻代和老年代,年轻代依然有Eden区和Survivor区。但从堆的结构上看,它不要求整个Eden区、年轻代或者老年代都是连续的,也不再坚持固定大小和固定数量。
- 将堆空间分为若干个区域( Region) ,这些区域中包含了逻辑上的年轻代和老年代。
- 和之前的各类回收器不同,它同时管理年轻代和老年代。对比其他回收器,或者工作在年轻代,或者工作在老年代G1所谓的分代,已经不是下面这样的了
图片说明:
一个region有可能属于Eden,Survivor或者old/Tenured内存区域。但是一个region只可能属于一个角色。
- E:表示该region属于Eden内存区域
- S:表示属于survivor内存区域
- O:表示属于01d内存区域
- 空白:表示未使用的内存空间。
- H:G1垃圾收集器还增加了一种新的内存区域,叫做Humongous内存区域,如图中的H块。主要用于存储大对象,如果超过1.5个region,就放到H。
设置H的原因?
对于堆中的大对象,默认直接会被分配到老年代,但是如果它是一个短期存在的大对象,就会 对垃圾收集器造成负面影响(宽泛意义上的内存泄露,不用了但是好长时间才释放)。为了解决这个问题,G1划分了一个Humongous区,它用来专门存放大对象。如果一个H区装不下一个大对象,那么G1会寻找连续的H区来存储。为了能找到连续的H区,有时候不得不启动Full GC。G1的大多数行为都把H区作为老年代的一部分来看待。
3)空间整合
4)可预测的停顿时间模型
3. G1参数设置
G1参数设置:
- -XX:+UseG1GC:手动指定使用G1垃圾收集器执行内存回收任务(jdk1.8可切换G1)
- -XX:G1HeapRegionSize设置每个Region的大小。值是2的幂,范围是1MB到32MB之间,目标是根据最小的Java堆大小划分出约2048个区域。默认是堆内存的1/2000。
- -XX:MaxGCPauseMillis 设置期望达到的最大Gc停顿时间指标(JVM会尽力实现,但不保证达到)。默认值是200ms
4. 应用场景
面向服务端应用,针对具有大内存、多处理器的机器。(在普通大小的堆里表现并不惊喜),正好是互联网项目特点。