JVM垃圾回收器

收集算法是内存回收的方法论,垃圾收集器就是内存回收的具体实现。收集器主要分三类:串行收集器、并行收集器以及并发收集器。

一、基础概念

1、并发和并行

a:并行(Parallel):指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。

b:并发(Concurrent):指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),用户程序在继续运行,而垃圾收集程序运行在另一个CPU上。

2、新生代 GC 和老年代GC

a:新生代GC(Minor GC):指发生在新生代的垃圾收集动作,因为Java对象大多都具备朝生夕灭的特性,所以Minor GC非常频繁,一般回收速度也比较快。

b:老年代GC(Major GC / Full GC):指发生在老年代的GC,出现了Major GC,经常会伴随至少一次的Minor GC(但非绝对的)。Major GC的速度一般会比Minor GC慢10倍以上。

3、吞吐量

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

虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%。

二垃圾收集器

1、Serial收集器

串行收集器是最古老,最稳定以及效率高的收集器,可能会产生较长的停顿,只使用一个线程去回收。

新生代、老年代使用串行回收;新生代复制算法、老年代标记-压缩;垃圾收集的过程中会Stop The World(服务暂停)

参数控制:-XX:+UseSerialGC 串行收集器

2、ParNew收集器

ParNew收集器其实就是Serial收集器的多线程版本。新生代并行,老年代串行;新生代复制算法、老年代标记-压缩

参数控制:-XX:+UseParNewGC ParNew收集器

-XX:ParallelGCThreads 限制线程数量

Serial收集器 VS ParNew收集器

ParNew收集器在单CPU的环境中绝对不会有比Serial收集器更好的效果,甚至由于存在线程交互的开销,该收集器在通过超线程技术实现的两个CPU的环境中都不能百分之百地保证可以超越Serial收集器。

然而,随着可以使用的CPU的数量的增加,它对于GC时系统资源的有效利用还是很有好处的。

3、Parallel Scavenge收集器

是一个新生代收集器,他也是使用复制算法的收集器,又是并行的多线程收集器。Parallel收集器更关注系统的吞吐量

也可以通过参数控制GC的时间不大于多少毫秒或者比例;新生代复制算法、老年代标记-压缩

参数控制:-XX:+UseParallelGC 使用Parallel收集器+ 老年代串行

Parallel Scavenge收集器 VS CMS等收集器

CMS等收集器的关注点是尽可能地缩短垃圾收集时用户线程的停顿时间,而Parallel Scavenge收集器的目标则是达到一个可控制的吞吐量(Throughput)。

由于与吞吐量关系密切,Parallel Scavenge收集器也经常称为"吞吐量优先"收集器。

Parallel Scavenge收集器 VS ParNew收集器

Parallel Scavenge收集器与ParNew收集器的一个重要区别是它具有自适应调节策略。

GC自适应的调节策略

Parallel Scavenge收集器有一个参数-XX:+UseAdaptiveSizePolicy。当这个参数打开之后,就不需要手工指定新生代的大小。

Eden与Survivor区的比例、晋升老年代对象年龄等细节参数了,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量,这种调节方式称为GC自适应的调

节策略(GC Ergonomics)。

4、Serial Old收集器

它是Serial收集器的老年代版,它同样是一个单线程收集器,使用"标记--整理"算法。

5、Parallel Old 收集器

是Parallel Scavenge收集器的老年代版,使用多线程与"标记--整理"算法。

6、CMS收集器

是一种以获取最短回收停顿时间为目标的收集器。目前很大一部分的java应用集中在互联网站或者B/S系统的服务端上,这类应用尤其重视服务的响应速度,希望系统停顿时间最短,以给用户带来较好的体验。

CMS收集器就非常符合这类应用的需求。

CMS收集器是基于"标记--清除"算法实现的, 他的运作过程相对于前几种收集器来说更复杂一些,整个过程分为4个步骤:

a、初始标记(CMS inital mark):需要"stop the world",但只标记一下GC Roots能直接关联的对象,速度很快。

b、并发标记(CMS concurrent mark):是GC Roots Tracing的过程,花费时间长

c、重新标记(CMS remark):是为了修正并发标记期间因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短。

d、并发清除(CMS concurrent sweep):是并发清除无用对象。

由于整个过程中耗时最长的并发标记和并发清除过程收集器线程都可以与用户线程一起工作,所以,从总体上来说,CMS收集器的内存回收过程是与用户线程一起并发执行的。

优点

CMS是一款优秀的收集器,它的主要优点在名字上已经体现出来了:并发收集、低停顿。

缺点

(1)CMS收集器对CPU资源非常敏感

在并发阶段,它虽然不会导致用户线程停顿,但是会因为占用了一部分线程(或者说CPU资源)而导致应用程序变慢,总吞吐量会降低。

(2)CMS收集器无法处理浮动垃圾

由于CMS并发清理阶段用户线程还在运行着,伴随程序运行自然就还会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后,CMS无法在当次收集中处理掉它们,只好留待下一次GC时再清理掉。这一部分垃

圾就称为"浮动垃圾"。

(3)CMS收集器会产生大量空间碎片

CMS是一款基于"标记---清除"算法实现的收集器,这意味着收集结束时会有大量空间碎片产生。

空间碎片过多时,将会给大对象分配带来很大麻烦,往往会出现老年代还有很大空间剩余,但是无法找到足够大的连续空间来分配当前对象,不得不提前触发一次Full GC。

7、G1收集器

G1(Garbage-First)是一款面向服务端应用的垃圾收集器。

与CMS收集器相比有以下优点:

(1)没有大量空间碎片

与CMS的"标记---清理"算法不同,G1从整体来看是基于"标记---整理"算法实现的收集器,从局部(两个Region之间)上来看是基于"复制"算法实现的。

(2)可预测的停顿

这是G1相对于CMS的另一大优势,降低停顿时间是G1和CMS共同的关注点,但G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的

时间不得超过N毫秒。

G1收集器执行过程

a、初始标记(Initial Marking)

初始标记阶段仅仅只是标记一下GC Roots能直接关联到的对象,并且修改TAMS的值,让下一阶段用户程序并发运行时,能在正确可用的Region中创建新对象,这阶段需要停顿线程,但耗时很短。

b、并发标记(Concurrent Marking)

并发标记阶段是从GC Root开始对堆中对象进行可达性分析,找出存活的对象,这阶段耗时较长,但可与用户程序并发执行。

c、最终标记(Final Marking)

最终标记阶段是为了修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录,虚拟机将这段时间对象变化记录在线程Remembered Set Logs里面,最终标记阶段需要把Remembered Set

Logs的数据合并到Remembered Set中,这阶段需要停顿线程,但是可并行执行。

d、筛选回收(Live Data Counting and Evacuation)

筛选回收阶段首先对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划,这个阶段其实也可以做到与用户程序一起并发执行,但是因为只回收一部分Region,时间是用户可控制

的,而且停顿用户线程将大幅提高收集效率。

三、常用的收集器组合

虽然我们是在对各个收集器进行比较,但并非为了挑选出一个最好的收集器。因为直到现在为止还没有最好的收集器出现,更加没有万能的收集器,所以我们选择的只是对具体应用最合适的收集器。

常用的收集器组合

|-----|-------------------|----------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| | 新生代GC策略 | 年老代GC策略 | 说明 |
| 组合1 | Serial | Serial Old | Serial和Serial Old都是单线程进行GC,特点就是GC时暂停所有应用线程。 |
| 组合2 | Serial | CMS+Serial Old | CMS(Concurrent Mark Sweep)是并发GC,实现GC线程和应用线程并发工作,不需要暂停所有应用线程。另外,当CMS进行GC失败时,会自动使用Serial Old策略进行GC。 |
| 组合3 | ParNew | CMS | 使用-XX:+UseParNewGC选项来开启。ParNew是Serial的并行版本,可以指定GC线程数,默认GC线程数为CPU的数量。可以使用-XX:ParallelGCThreads选项指定GC的线程数。 如果指定了选项-XX:+UseConcMarkSweepGC选项,则新生代默认使用ParNew GC策略。 |
| 组合4 | ParNew | Serial Old | 使用-XX:+UseParNewGC选项来开启。新生代使用ParNew GC策略,年老代默认使用Serial Old GC策略。 |
| 组合5 | Parallel Scavenge | Serial Old | Parallel Scavenge策略主要是关注一个可控的吞吐量:应用程序运行时间 / (应用程序运行时间 + GC时间),可见这会使得CPU的利用率尽可能的高,适用于后台持久运行的应用程序,而不适用于交互较多的应用程序。 |
| 组合6 | Parallel Scavenge | Parallel Old | Parallel Old是Serial Old的并行版本 |
| 组合7 | G1GC | G1GC | -XX:+UnlockExperimentalVMOptions -XX:+UseG1GC #开启 -XX:MaxGCPauseMillis =50 #暂停时间目标 -XX:GCPauseIntervalMillis =200 #暂停间隔目标 -XX:+G1YoungGenSize=512m #年轻代大小 -XX:SurvivorRatio=6 #幸存区比例 |

图形展示

图中展示了7种作用于不同分代的收集器,如果两个收集器之间存在连线,就说明它们可以搭配使用。虚拟机所处的区域,则表示它是属于新生代收集器还是老年代收集器。

相关推荐
九圣残炎19 分钟前
【从零开始的LeetCode-算法】1456. 定长子串中元音的最大数目
java·算法·leetcode
wclass-zhengge21 分钟前
Netty篇(入门编程)
java·linux·服务器
lulu_gh_yu24 分钟前
数据结构之排序补充
c语言·开发语言·数据结构·c++·学习·算法·排序算法
丫头,冲鸭!!!44 分钟前
B树(B-Tree)和B+树(B+ Tree)
笔记·算法
Re.不晚1 小时前
Java入门15——抽象类
java·开发语言·学习·算法·intellij-idea
雷神乐乐1 小时前
Maven学习——创建Maven的Java和Web工程,并运行在Tomcat上
java·maven
码农派大星。1 小时前
Spring Boot 配置文件
java·spring boot·后端
顾北川_野1 小时前
Android 手机设备的OEM-unlock解锁 和 adb push文件
android·java
江深竹静,一苇以航1 小时前
springboot3项目整合Mybatis-plus启动项目报错:Invalid bean definition with name ‘xxxMapper‘
java·spring boot
confiself1 小时前
大模型系列——LLAMA-O1 复刻代码解读
java·开发语言