垃圾收集器

11 篇文章 0 订阅

概述

串行回收器:Serial、Serial Old

并行回收器:ParNew、Parallel Scavenge、Parallel Old

并发回收器:CMS、G1

年轻代回收器:Serial GC、Parallel Scavenge GC、ParNew GC、G1 GC

老年代回收器:Serial Old GC、Parallel Old GC、CMS GC、G1 GC

评估GC的性能指标

暂停时间:

暂停时间是指一个时间段内应用程序线程暂停,让GC线程执行的状态

暂停时间优先是指尽可能让单次STW的时间最短

吞吐量:

吞吐量是CPU用于运行用户代码的时间与CPU总消耗时间的比值,即吞吐量=运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间)

吞吐量优先是指在单位时间内,STW的时间最短

垃圾回收的串行,并行,并发

并行:多条垃圾收集线程并行工作,但此时用户线程仍处于等待状态

串行:单线程执行,如果内存不够,则程序暂停,启动JVM垃圾回收器进行垃圾回收,回收完再启动程序的线程

并发:用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),垃圾收集线程在执行时不会停顿用户程序的运行

安全点与安全区域

安全点(Safe Point)

程序执行时并非在所有地方都能停顿下来开始GC,只有在“安全点”(Sate Point)才能停顿下来开始GC

安全点太少可能会导致GC等待时间太长,如果太频繁可能会导致运行时的性能问题,通常安全点会根据是否具有让程序长时间执行的特征为标准,选择一些执行时间较长的指令作为Safe Point(如:方法调用、循环跳转、异常跳转等)

中断

抢占式中断:(目前没有虚拟机采用了)

首先中断所有线程,如果还有线程不在安全点,就恢复线程,让线程跑到安全点

主动式中断:

设置一个中断标志,各个线程运行到Safe Point的时候主动轮询这个标志,如果中断标志为真,则将自己进行中断挂起

安全区域(Safe Region)

线程处于Sleep状态或Blocked状态时,无法响应中断请求,无法走到安全点去中断挂起,JVM也不会等待线程被唤醒,此时就需要安全区域来解决

安全区域是指一段代码片段中,对象的引用关系不会发生变化,在这个区域中的任何位置开始GC都是安全的。Safe Region相当于是扩展了的Safe Point

Stop-The-Word

简称STW,是指GC事件发生过程中,会产生应用程序的停顿,停顿产生时整个应用程序线程都会被暂停,没有任何响应

串行垃圾收集器

串行垃圾收集器,是指使用单线程进行垃圾回收,垃圾回收时,只有一个线程在工作,并且java应用中的所有线程都要暂停,等待垃圾回收的完成。这种现象称之为STW(Stop-The-World)。

对于交互性较强的应用而言,这种垃圾收集器是不能够接受的。

一般在Javaweb应用中是不会采用该收集器的。

Serial垃圾收集器

年轻代使用Serial收集器,老年代使用Serial Old收集器

Serial 收集器采用复制算法、串行回收、Stop-The-World机制执行内存回收

Serial Old 收集器采用标记压缩算法、串行回收、Stop-The-World机制执行内存回收

这个收集器是一个单线程收集器,它的单线程的意义并不仅仅说明它只会使用一个CPU或一条收集线程去完成垃圾收集工作,更重要的是在它进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束

优势:简单而高效,对于限定单个CPU的坏境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程收集效率;适合于运行在Client模式下的虚拟机

并行垃圾收集器

并行垃圾收集器在串行垃圾收集器的基础之上做了改进,将单线程改为了多线程进行垃圾回收,这样可以缩短垃圾回收的时间。(这里是指,并行能力较强的机器)

并行垃圾收集器在收集的过程中也会暂停应用程序,这个和串行垃圾回收器是一样的,只是并行执行,速度更快些,暂停的时间更短一些。

ParNew垃圾收集器

ParNew垃圾收集器是工作在年轻代上的,只是将串行的垃圾收集器改为了并行。

ParNew垃圾收集器是Serial收集器的多线程版本,ParNew收集器除了采用并行回收的方式执行内存回收外,和Serial收集器几乎没有任何区别

ParNew收集器在年轻代中同样也是采用复制算法、Stop-The-World机制

在单个CPU坏境下,ParNew收集器不比Serial收集器更高效

Parallel GC垃圾收集器

Parallel GC收集器工作机制和ParNew GC收集器一样,只是在此基础之上,新增了两个和系统吞吐量相关的参数,使得其使用起来更加的灵活和高效。

Parallel Scavenge收集器同样采用了复制算法、并行回收、Stop-The-World机制

Parallel Old收集器采用了标记压缩算法,并行回收,Stop-The-World机制

Parallel Scavenge收集器的目标是达到一个可控制的吞吐量,被称为吞吐量优先的垃圾收集器;支持自适应策略(高吞吐量可以高效率地利用CPU时间,尽快完成程序地运算任务,主要适合在后台运算而不需要太多交互的任务)

并发垃圾收集器

CMS垃圾收集器

CMS全称 Concurrent Mark Sweep,是HotSpot虚拟机中第一款真正意义上的并发收集器,实现了让垃圾收集线程与用户线程同时工作

CMS的垃圾收集算法采用标记-清除算法,并且也会Stop-The-World

CMS垃圾回收器的执行过程如下:

初始标记(Initial-mark)阶段:在这个阶段中,程序中所有的工作线程都将会因为Stop-The-World机制而出现短暂的暂停,这个阶段的主要任务仅仅只是标记出GC Roots能直接关联到的对象,一旦标记完成之后就会恢复之前被暂停的所有应用线程。由于直接关联对象比较小,所以这里的速度非常快

并发标记(Concurrent-mark)阶段:从GC Roots的直接关联对象开始遍历整个的对象图的过程,这个过程耗时较长但是不需要停顿用户线程,可以与垃圾收集线程一起并发运行

重新标记(Remark)阶段:由于并发标记阶段中,程序的工作线程和垃圾收集线程同时运行或者交叉运行,因此为了修正并发标记期间,因用户线程继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间通常会比初始标记阶段稍长一些,但远比并发标记阶段的时间短

并发清除(Concurrent-sweep)阶段:此阶段清理删除掉标记阶段判断的已经死亡的对象,释放内存空间。由于不需要移动存活对象,所以这个阶段也是可以与用户线程同时并发的

CMS触发时机

由于垃圾收集阶段用户线程没有中断,所以在CMS回收过程中,还应该确保应用程序用户线程有足够的内存可用。所以CMS是当堆内存使用率达到某一阈值时,便开始进行回收,以确保应用程序在CMS工作过程中依然有足够的空间支持应用程序运行。要是CMS运行期间预留的内存无法满足程序要求,就会出现一次“Concurrent Mode Failure”失败,这是虚拟机将启动后备预案:临时启用Serial Old收集器来重新进行老年代的垃圾收集,这样停顿时间就长了。

时机:如果内存增长缓慢,可以设置一个稍大的值,大的阈值可以有效降低CMS的触发频率,减少老年代回收的次数,较为明显地改善 应用程序地性能。如果应用程序内存使用率增长很快,则可以降低这个阈值,以避免频繁触发老年代串行收集器,可以有效降低Full GC的执行次数

优缺点

优点:并发收集;低延迟

缺点:

会产生内存碎片,在无法分配大对象的情况下,不得不提前触发Full GC;

CMS收集器对CPU资源非常敏感,在并发阶段,因为占用了一部分线程而导致应用程序变慢,总吞吐量会降低

CMS无法收集浮动垃圾,在并发标记阶段由于程序的工作线程和垃圾收集线程是同时运行或者交叉运行,那么在并发标记阶段如果产生新的垃圾对象,CMS将无法对这些垃圾对象进行标记,最终会导致这些新的产生的垃圾对象没有被及时回收,从而只能在下一次执行GC时释放这些之前未被回收的内存空间

G1垃圾收集器

G1(Garbage First)是一个并行回收器,它把堆内存分割为很多不相干的区域(Region)(物理上不连续的),使用不同的Region来表示Eden、Survivor0区、Survivor1区、年老代等。G1跟踪各个Region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region(垃圾优先)

Regin使用指针碰撞和TLAB

G1收集器将Java堆分成约2048个大小相同的独立Region,所有Region大小相同,且在JVM生命周期内不会被改变;一个region有可能属于Eden,Survivor或者Old/Tenured内存区域,但是一个Region只能属于一个角色

G1垃圾收集器还增加了一种新的内存区域,叫做Humongous内存区域(H区),主要用于存储大对象,如果超过1.5个region,就放到H区,如果一个H区装不下一个大对象,那么G1会寻找连续的H区来存储

G1主要针对配备多核CPU及大容量内存的机器,以极高概率满足GC停顿时间的同时,还兼具高吞吐量的性能特征

G1中提供了三种模式垃圾回收模式,Young GC、Mixed GC 和 Full GC,在不同的条件下被触发。

特点:

并行与并发:

并行性:G1在回收期间,可以有多个GC线程同时工作,有效利用多核计算能力,此时用户线程STW

并发性:G1拥有与应用线程交替执行的能力,部分工作可以和应用程序同时执行

分代收集:

G1属于分代型垃圾回收器,区分年轻代,老年代,但是不要求连续,也不坚持固定大小和固定数量,收集时同时兼顾年轻代和老年代

空间整合:

G1将内存划分为一个个的region,内存回收以region为基本单位,Region之间是复制算法,但整体可看作是标记压缩算法,可以避免内存碎片

可预测的停顿时间模型:

G1可以建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不超过N毫秒

G1可以只选取部分区域进行内存回收,缩小回收范围,对于全局停顿情况的发生也能得到较好的控制

适用场景

面向服务端应用,针对具有大内存、多处理器的机器

最主要的应用是需要低GC延迟,并具有大堆的应用程序提供解决方案

原理

G1垃圾收集器相对比其他收集器而言,最大的区别在于它取消了年轻代、老年代的物理划分,取而代之的是将堆划分为若干个区域(Region),这些区域中包含了有逻辑上的年轻代、老年代区域。这样做的好处就是,我们再也不用单独的空间对每个代进行设置了,不用担心每个代内存是否足够。

在G1划分的区域中,年轻代的垃圾收集依然采用暂停所有应用线程的方式,将存活对象拷贝到老年代或者Survivor空间,G1收集器通过将对象从一个区域复制到另外一个区域,完成了清理工作。这就意味着,在正常的处理过程中,G1完成了堆的压缩(至少是部分堆的压缩),这样也就不会有cms内存碎片问题的存在了。

在G1中,有一种特殊的区域,叫Humongous区域。

如果一个对象占用的空间超过了分区容量50%以上,G1收集器就认为这是一个巨型对象。这些巨型对象,默认直接会被分配在老年代,但是如果它是一个短期存在的巨型对象,就会对垃圾收集器造成负面影响。为了解决这个问题,G1划分了一个Humongous区,它用来专门存放巨型对象。如果一个H区装不下一个巨型对象,那么G1会寻找连续的H分区来存储。为了能找到连续的H区,有时候不得不启动Full GC。

G1 GC的垃圾回收过程主要包括三个环节:年轻代GC(Young GC)、老年代并发标记过程(Concurrent Marking)、混合回收(Mixed GC)。(如果需要,单线程、独占式、高强度的Full GC还是继续存在的,它针对GC的评估失败提供了一种失败保护机制,即强力回收)

G1回收过程:

概述:

应用程序分配内存,当年轻代的Eden区用尽时开始年轻代回收过程,G1的年轻代收集阶段是一个并行的独占式收集器,在年轻代回收期,G1 GC暂停所有应用程序线程,启动多线程执行年轻代回收,然后从年轻代区间移动存活对象到Survivor区间或者年老代区间,也有可能是两个区间都会涉及

当堆内存使用达到一定值(默认45%)时,开始老年代并发标记过程

标记完成马上开始混合回收过程,对于一个混合回收期,G1 GC从年老代区间移动存活对象到空闲区间,这些空闲区间也就成为了年老代的一部分,年老代回收器不需要整个年老代都被回收,一次只需要扫描/回收一小部分年老代的Regino就可以了。同时,这个年老代Region和年轻代一起被回收的 

Remembered Set(已记忆集合)

一个Region不可能是孤立的,一个Region中的对象可能被其他任意Region中对象引用,判断对象存活时,不能扫描整个Java堆,这样会降低GC的效率,所以使用Remembered Set

解决方法:

无论G1还是其他分代收集器,JVM都是使用Remembered Set来避免全局扫描

每个Region都有一个对应的Remembered Set

每次Reference类型数据写操作时,都会产生一个Write Barrier暂时中断操作,然后检查将要写入的引用指向的对象是否和该Reference类型数据在不同的Region(其他收集器:检查老年代对象是否引用了新生代对象),如果不同,通过CardTable把相关引用信息记录到引用指向对象的所在Region对应的Remembered Set中

当进行垃圾收集时,在GC根节点的枚举范围加入Remembered Set,就可以保证不进行全局扫描,也不会有遗漏

回收过程一:年轻代GC

第一阶段:扫描根。根是指static变量指向的对象,正在执行的方法调用链条上的局部变量等。跟引用连同RSet记录的外部引用作为扫描存活对象的入口

第二阶段:更新RSet。此阶段完成后,RSet可以准确的反应老年代对所在的内存分段中对象的引用

第三阶段:处理RSet。识别被老年代对象指向的Eden中的对象,这些被指向的Eden中的对象被认为是存活对象

第四阶段:复制对象。

第五阶段:处理引用。处理强软弱虚等引用

回收过程二:并发标记过程

第一阶段:初始标记过程。标记从根节点直接可达的对象,这个阶段是STW的,并且会触发一次年轻代GC

第二阶段:根区域扫描。G1 GC扫描Survivor区直接可达的老年代区域对象,并标记被引用的对象,这一过程必须在Young GC之前完成

第三阶段:并发标记。在这个堆中进行并发标记(和应用程序并发执行),此过程可能被young GC中断,在并发标记阶段,若发现区域对象中的所有对象都是垃圾,那这个区域会被立即回收,同时,并发标记过程中,会计算每个区域的对象活性(区域中对象存活比例)

第四阶段:再次标记。修正上一次标记的结果,会STW

第五阶段:独占清理。计算各个区域的存活对象和GC回收比例,并进行排序,识别可以混合回收的区域,会STW

第六阶段:并发清理阶段。识别并清理完全空闲的区域

回收过程三:混合回收

当越来越多的对象晋升到老年代old region时,为了避免堆内存被耗尽,虚拟机会触发一个混合的垃圾收集器,即Mixed GC,该算法并不是一个Old GC,除了回收整个Young Region,还会回收一部分的Old Region,这里需要注意:是一部分老年代,而不是全部老年代,可以选择哪些old region进行收集,从而可以对垃圾回收的耗时时间进行控制。也要注意的是Mixed GC 并不是 Full GC。

并发标记结束后,老年代中百分百为垃圾的内存分段被回收了,部分为垃圾的内存分段被计算出来了,默认情况下,这些年老代的内存会分8次(可设置)被回收

混合回收的会收集包括八分之一的年老代内存分段,Eden区内存分段,Survivor区内存分段。混合回收的算法和年轻代回收的算法完全一样,只是回收集多了年老代的内存分段

由于年老代中的内存被默认分8次回收,G1会优先收集垃圾多的内存分段,并且有一个阈值会决定内存分段是否被回收(可设置)

混合回收不一定要进行8次,有一个阈值(默认为10%,可设置),意思是允许整个堆空间中有10%的空间被浪费,意味着如果发现可以回收的垃圾占堆内存的比例低于10%,则不再进行混合回收。

可选过程四:Full GC

G1的初衷是避免Full GC的出现,但是如果上述方式不能正常工作,G1会停止应用程序的执行,使用单线程的内存回收算法进行垃圾回收,性能非常差,停顿时间很长

触发原因:Evacuation的时候没有足够的to空间来存放晋升的对象;并发处理过程完成之前空间耗尽

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值