JVM:垃圾回收器

JVM:垃圾回收器,第1张

JVM:垃圾回收器 GC:垃圾回收或者垃圾收集器

垃圾回收器概述:

垃圾收集器没有在规范中进行过多的规定,可以由不同的厂商,不同版本的JVM来实现

垃圾收集器分类:

按用于垃圾回收的线程数分,分为串行垃圾回收器和并行垃圾回收器

串行回收指的是在同一时间段内只允许有一个CPU用于执行垃圾回收 *** 作,此时工作线程被暂停,直至垃圾收集工作结束
在诸如单CPU处理器或者较小的应用内存等硬件平台不是特别优越的场合,串行回收器的性能表现可以超过并行回收器和并发回收器,所以,串行回收默认被应用在客户端的Client模式下的JVM中
在并发能力比较强的CPU上,并行回收器产生的停顿时间要断于串行回收器
串行回收仍然与并行回收一样,采用独占式,使用STW机制

按照工作模式分:
分为并发式垃圾回收器和独占式垃圾回收器
并发式垃圾回收器与应用程序线程交替工作,以尽可能减少应用程序的停顿时间
独占式垃圾回收器一旦运行,就停止应用程序中的所有用户线程,知道垃圾回收过程完全结束

按照碎片处理方式分:
压缩式垃圾回收器和非压缩式垃圾回收器
压缩式垃圾回收器会在垃圾回收完成后,对存活对象进行压缩整理,消除回收后的碎片
非压缩式垃圾回收器不进行这步 *** 作

按工作的内存区间分:
分为年轻代垃圾回收器和老年代垃圾回收器

评估GC的性能指标:

吞吐量:运行用户代码的时间占总运行时间的比例
总运行时间:程序的运行时间+内存回收的时间

垃圾收集开销:吞吐量的补数,内存回收的时间占总运行时间的比例
暂停时间:执行垃圾收集时,程序的工作线程被暂停的时间
收集频率:相对于应用程序的执行,收集 *** 作发生的频率
内存占用:Java堆所占的内存大小
主要抓以上两点:
吞吐量,暂停时间
吞吐量:比如:虚拟机总共运行了100分钟,其中垃圾收集花掉了1分钟,那吞吐量就是99%
高吞吐量和低暂停时间是一对相互竞争的目标,如果要选择以吞吐量优先,那么必然需要降低内存回收的执行频率,但是这样会导致GC需要更长的暂停时间来执行内存回收
现在标准:在最大吞吐量优先的情况下,降低停顿时间

7款经典的垃圾收集器:

串行回收器:Serial,Serial Old
并行回收器:ParNew、Parallel Scavenge、Parallel Old
并发回收器:CMS、G1

新生代收集器:Serial、ParNew、Parallel Scavenge
老年代收集器:Serial Old、Parallel Old、CMS
整堆收集器:G1

如何查看默认的垃圾收集器?

-XX:+PrintCommandLineFlags:查看命令行相关参数(包含使用的垃圾收集器)
使用命令行指令:jinfo -flag 相关垃圾回收器参数 进程ID

Serial回收器:串行回收

Serial收集器是最基本、历史最悠久的垃圾收集器了,JDK1.3之前回收新生代唯一的选择

Serial收集器作为HotSpot中Client模式下的默认新生代垃圾收集器

Serial收集器采用复制算法,串行回收和STW机制的方式来回收内存

除了年轻代之外,Serial收集器还提供用于执行老年代垃圾收集的Serial Old收集器,Serial Old收集器同样也采用串行回收和STW机制,只不过内存回收算法使用的是标记-压缩算法

Serial Old是运行在Client模式下默认的老年代的垃圾回收器

Serial Old在Server模式下主要有两个用途:

1、与新生代的Parallel Scavenge配合使用

2、作为老年代CMS收集器的后备垃圾收集方案

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

优势:简单而高效(与其他收集器的单线程比),对于限定单个CPU的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得最高的的单线程收集效率,运行在Client模式下的虚拟机是个不错的选择

在HotSpot虚拟机中,使用-XX:+UseSerialGC参数可以指定年轻代和老年代都使用串行收集器,等价于新生代用Serial GC,且老年代用Serial Old GC

ParNew回收器:并行回收

如果说Serial GC是年轻代中的单线程垃圾收集器,那么ParNew收集器则是Serial收集器的多线程版本,Par是Parallel的缩写,New:只能处理的是新生代

ParNew收集器除采用并行回收的方式执行内存回收外,两款垃圾收集器之间几乎没有任何区别,ParNew收集器在年轻代中同样采用复制算法,STW机制

ParNew是很多JVM运行在Server模式下新生代的默认垃圾收集器

对于新生代,回收次数频繁,使用并行方式高效,采取复制算法

对于老年代,回收次数少,使用串行方式节省资源(CPU并行需要切换线程,串行可以省去切换线程的资源),老年代采取标记-整理算法

在程序中,开发人员可以通过选项”-XX:+UseParNewGC“手动指定使用ParNew收集器执行内存回收任务,它表示年轻代使用并行收集器,不影响老年代

-XX:ParallelGCThreads限制线程数量,默认开启和CPU数据相同的线程数

Parallel Scavenge回收器:吞吐量优先:

Paralle Scavenge收集器同样采用复制算法,并行回收和STW机制

和ParNew不同,Parallel Scavenge的目标是达到一个可控制的吞吐量,它也被称之为吞吐量优先的垃圾收集器

自适应调节策略也是Parallel Scavenge与ParNew的一个重要区别

高吞吐量则可以高效率地利用CPU时间,尽快完成程序的运算任务j,主要适合在后台运算j而不需要太多交互的任务

Parallel收集器在jdk1.6时提供用于执行老年代垃圾收集的Parallel Old收集器,用来代替老年代的Serial Old收集器

Parallel Old收集器采用了标记-压缩算法,但同样也是基于并行回收和STW

参数配置:

-XX:+UseParallelGC 手动指定年轻代使用Parallel并行收集器执行内存回收任务

-XX:+UseParallelOldGC 手动指定老年代使用并行垃圾回收器
默认jdk8是开启的
上面两个参数默认开启一个,另一个也会被开启(互相激活)

-XX:ParallelGCThreads 设置年轻代并行垃圾收集器的线程数,一般地,最好与CPU数量相等,以避免过多的线程数影响垃圾收集性能
在默认情况下,当CPU数量小于8个,该值等于CPU数量
CPU数量大于8,该值等于3+[5 * CPU_Count] / 8]

-XX:MaxGCPauseMillis:设置垃圾收集器最大停顿时间(即STW的时间),单位是毫秒
为了尽可能地把停顿时间控制在MaxGCPauseMillis以内,收集器在工作时会调整Java堆大小或者其他一些参数
对于用户来说(客户端),停顿时间越短体验越好,但是在服务器端,我们注重高并发,整体的吞吐量,所以服务器端适合Parallel,进行控制
该参数使用需谨慎

-XX:GCTimeRatio 垃圾收集时间占总时间的比例

-XX:+UseAdaptiveSizePolicy 设置Parallel Scavenge收集器具有自适应调节策略
在这种模式下,年轻代的大小,Eden和Survivor的比例,晋升老年代的对象年龄等参数会被自动调整,已达到在堆大小、吞吐量和停顿时间之间的平衡点
在手动调优比较困难的场合,可以直接使用这种自适应的方式,仅指定虚拟机的最大堆、目标的吞吐量和停顿时间,让虚拟机自己完成调优工作

jinfo -flag UseAdaptiveSizePolicy 进程id,查看到默认开启状态

CMS回收器:低延迟

在jdk1.5时期,HotSpot推出了一款在强交互应用中几乎可认为有划时代意义的垃圾收集器:CMS(Concurrent-Mark-Sweep)收集器,这款收集器是HotSpot虚拟机中第一款真正意义上的并发收集器,它第一实现了垃圾收集线程与用户线程同时工作

该收集器采用标记-清除算法,并且也会STW

不幸的是,CMS作为老年代的收集器,由于底层垃圾回收用的框架和jdk1.4.0中已存在的新生代收集器Parallel Scavenge不一样,所以不能一起工作,在jdk1.5中使用CMS来收集老年代,新生代只能选择ParNew或者Serial

工作原理:
整个过程分为4个阶段:
初始标记阶段、并发标记阶段、重新标记阶段、并发清除阶段
初始标记阶段:
会出现STW,即所有的工作线程会出现短暂的暂停,该阶段仅仅只是标记出GC Roots能直接关联到的对象,一旦标记完成就会恢复之前被暂停的所有应用线程,由于直接关联对象比较小,所以这里的速度非常快
并发标记阶段:
从GC Roots的直接关联对象开始遍历整个对象图的过程,这个过程耗时较长但是不需要停顿用户线程,可以与垃圾收集线程一起并发运行
重新标记:由于在并发标记阶段,程序的工作线程会和垃圾收集线程同时运行或者交叉运行,因此为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间通常会比初始标记阶段稍长一些,但也远比并发标记阶段的时间短,此阶段会STW
并发清除阶段:
清理标记阶段判断的已经死亡的对象,释放内存空间

由于在垃圾收集阶段用户线程没有中断,所以在CMS回收过程中,还应该确保应用程序用户线程有足够的内存可用,因此,CMS收集器不能像其他收集器那样等到老年代几乎完全被填满了再进行收集,而是当堆内存使用率达到某一阈值时,便开始进行回收,要是cms运行期间预留的内存无法满足程序需要,就会出现一次"Concurrent Mode Failure"失败,这时虚拟机将启动后备方案,临时启动Serial Old收集器来重新进行老年代的垃圾收集,这样停顿时间就很长了

有人会觉得既然Mark Sweep会造成内存碎片,那么为什么不把算法换成Mark Compact呢?
并发清除阶段,用Compact整理内存的话,会导致原来的用户线程使用的内存收到影响,Mark Compact更适合STW情况

优点:
并发收集
低延迟
弊端:
1:会产生内存碎片,导致并发清除后,用户线程可用的空间不足,在无法分配大对象的前提下,不得不提前触发Full GC
2:导致总吞吐量会降低,并发阶段虽然不会导致用户停顿,但是会因为占用了一部分线程而导致应用程序变慢
3:无法处理浮动垃圾。。

CMS收集器可以设置的参数

-XX:+UseConcMarkSweepGC 手动指定使用CMS收集器执行内存回收任务
开启该参数后,会自动将-XX:+UseParNewGC打开。即ParNew(Young区用)+CMS(Old区用)+Serial Old的组合

-XX:CMSInitingOccupanyFraction设置堆内存使用率的阈值,一旦达到该阈值就进行回收
jdk5及以前版本的默认值是68%,即当老年代的空间使用率达到68%时,会执行一次CMS回收,jdk6及以上版本默认值为92%
如果内存增长缓慢,则可以设置一个稍大的值,大的阈值可以有效降低CMS的触发频率,减少老年代回收的次数可以较为明显改善应用程序性能,反之,如果应用程序内存使用率增长很快,则应该降低这个阈值,以避免频繁地触发老年代串行收集器,因此通过该选项便可以有效降低Full GC的执行次数

-XX:+UseCMSCompactAtFullCollection用于指定在执行完Full GC之后对内存空间进行压缩整理,一次避免内存碎片的产生,不过由于内存压缩整理过程无法并发执行,所带来的停顿时间更长了

-XX:CMSFullGCsBeforeCompaction设置在执行多少次Full GC后对内存空间进行压缩整理

-XX:ParallelCMSThreads设置CMS的线程数量
CMS默认启动的线程数是(ParallelGCThreads+3)/4,ParallelGCThreads是年轻代并行收集器的线程数,当CPU资源比较紧张时,受到CMS收集器线程的影响,应用程序的性能在垃圾回收阶段可能会非常糟糕

垃圾收集器的选择:

想要最小化地使用内存和并行开销:Serial GC
想要最大化应用程序的吞吐量:Parallel GC
想要最小化GC的中断或停顿时间:CMS GC

jdk后续版本中CMS的变化

jdk9新特性:CMS被标记为Deprecate了
若此时用参数开启CMS,会受到警告信息,提示CMS未来会被废弃

jdk14:删除CMS垃圾回收器
若此时用参数开启CMS,jvm不会报错,只是给出一个warning信息,但是不会exit,jvm会自动回退以默认GC方式启动jvm

G1回收器:区域化分代式

官方给G1设定的目标是在延迟可控的情况下获得尽可能高的吞吐量

在jdk1.7版本正式启用,移除了Experimental的标识,是jdk9以后的默认垃圾回收器,取代了CMS回收器以及Parallel + Parallel Old组合。被Oracle官方称为“全功能的垃圾收集器”。在jdk8中还不是默认的垃圾回收器,需要使用-XX:+UseG1GC来启用

为什么名字叫Garbage First(G1)呢?

G1是一个并行回收器,它把堆内存分割为很多不相关的区域(物理上不连续的),使用不同的Region来表示Eden、幸存者0区、幸存者1区、老年代等,它有计划地避免在整个Java堆中进行全区域的垃圾回收,G1跟踪各个Region里面的垃圾堆积的价值大小(回收所获得的空间大小和回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region,由于这种方式的侧重点在于回收垃圾最大量的区间,所以叫做垃圾优先(Garbage First)

G1回收器的特点(优势)

并行与并发:
并行性:在回收期间,可以有多个GC线程同时工作,有效利用多核计算能力,此时用户线程STW
并发性:G1拥有与应用程序交替执行的能力,部分工作可以和应用程序同时执行,因此,一般来说,不会在整个回收阶段发生完全阻塞应用程序的情况

分代收集:
从堆结构上看,它不要求整个Eden区,年轻代,或者老年代都是连续的,也不再坚持固定大小和固定数量
和之前的各类回收器不同,它同时兼顾年轻代和老年代

空间整合:
CMS:标记清除算法、内存碎片、若干次GC后进行一次碎片整理
G1将内存划分为一个个region,内存的回收是以region作为基本单位,Region之间是复制算法,但整体上可看作标记-压缩算法,两种算法都可以避免内存碎片

可预测的停顿时间模型(即:软实时soft real-time)
这是G1相对于CMS的另一大优势,G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒
由于分区的原因,G1可以只选取部分区域进行内存回收,这样缩小了回收的范围,因此对于全局停顿情况的发生也能得到较好的控制
G1跟踪各个Region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region。保证了G1收集器在有限的时间内可以获取尽可能高的收集效率。

G1回收器的缺点:

相比较于CMS,G1还不具备全方位,压倒性优势,比如在用户程序运行过程中,G1无论是为了垃圾收集产生的内存占用还是程序运行时的额外执行负载都要比CMS高

从经验上,在小内存应用上CMS的表现大概率会优于G1,而G1在大内存应用上则发挥其优势,平衡点在6-8GB之间

G1回收器的参数设置:

-XX:+UseG1GC 手动指定使用G1收集器执行内存回收任务
-XX:G1HeapRegionSize 设置每个Region的大小,值是2的幂,范围是1MB到32MB之间,目标是根据最小的Java堆大小划分出约2048个区域,默认是堆内存的1/2000
-XX:MaxGCPauseMills 设置期望达到的最大GC停顿时间指标(JVM会尽力实现,但不保证达到),默认值是200ms

G1回收器的常见 *** 作步骤:

第一步:开启G1垃圾收集器
第二步:设置堆的最大内存
第三步:设置最大的停顿时间

G1回收器的适用场景

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

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

如:在堆大小约6GB或更大时,可预测的暂停时间可以低于0.5秒

用来替换jdk1.5中的CMS收集器
在下面的情况时,使用G1可能比CMS好
1:超过50%的Java堆被活动数据占用
2:GC停顿时间过长(长于0.5至1秒)
3:对象分配频率或年代提升频率变化很大

分区Region:化整为0

使用G1收集器时,它将整个Java堆划分成约2048个大小相同的独立Region块,每个Region块大小根据堆空间的实际大小而定,整体被控制在1M-32M,所有的Region大小相同,且在JVM生命周期内不会被改变

一个Region有可能属于Eden、Survivor、或者Old区域,但是一个Region只可能属于一个角色

G1垃圾收集器还增加了一种新的内存区域,叫做Humongous内存区域,主要用于存储大对象,如果超过1.5个region,就放到H

对于堆中的大对象,默认直接会被分配到老年代,但是如果它是一个短期存在的大对象,就会对垃圾器造成负面影响,该区就是为了解决这个问题,如果一个H区装不下一个大对象,那么G1会寻找连续的H区来存储,为了能找到连续的H区,有时候不得不启动Full GC

G1回收器垃圾回收过程:

主要包括如下三个

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5562876.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-14
下一篇 2022-12-14

发表评论

登录后才能评论

评论列表(0条)

保存