JVM垃圾回收器,GC策略调整参数,学习笔记

此为学习笔记,参照大神链接: 深入理解 JVM 垃圾回收机制及其实现原理.
在这里插入图片描述

名词解释

并发,并行,串行-学习笔记

  • 串行收集器Serial Collector:以串行模式工作的收集器;
  • 并行收集器Paraller Collector:以并行模式工作的收集器;
  • 并行(Parallel)【美 /ˈpærəlel/】:并行描述的是多条垃圾收集器线程之间的关系,说明同一时间有多条这样的线程在协同工作,通常默认此时用户线程是处于等待状态

  • 并发(Concurrent)【美 /kənˈkɜːrənt/】:并发描述的是垃圾收集器线程用户线程之间的关系,说明同一时间垃圾收集器线程与用户线程都在运行。由于用户线程并未被冻结,所以程序仍然能响应服务请求,但由于垃圾收集器线程占用了一部分系统资源,此时应用程序的处理的吞吐量将受到一定影响。

  • ----------------本段摘自《深入理解Java虚拟机》

  • 吞吐量(Throughput): CPU 中用于运行用户代码的时间与 CPU 总消耗时间的比值;吞吐量 = 运行用户代码时间 /(运行用户代码时间 + 垃圾收集时间) 如虚拟机总运行了 100 分钟,其中垃圾收集花掉 1 分钟,那吞吐量就是99%。
    在这里插入图片描述

  • Stop-the-world:GC线程与应用线程保持相对独立,当系统需要执行垃圾回收任务时,先停止工作线程,应用程序进入暂停状态,然后命令 GC 线程工作。

  • SafePoint是什么
    每当发生一次垃圾收集的动作,所有的用户线程都必须跑到最近的一个安全点(SafePoint),然后挂起线程等待垃圾回收。
    在这里插入图片描述

垃圾回收器梗概

Serial 收集器 【新】

【美 /ˈsɪriəl/ 】

  1. 单线程收集器
  2. “Stop The World”
    用户体验不好,要是你的电脑每运行一个小时就会暂停响应五分钟,你会有什么样的心情?
  3. 新生代采用标记-复制算法,老年代采用标记-整理算法。
  4. 运行在 Client 模式下的虚拟机来说是个不错的选择。

在这里插入图片描述

Serial Old 收集器 【老】

Serial 收集器的老年代版本
在这里插入图片描述

ParNew 收集器 【新】

  1. Serial 收集器的多线程版本
  2. “Stop The World”
  3. 新生代采用标记-复制算法,老年代采用标记-整理算法。
  4. 它是许多运行在 Server 模式下的虚拟机的首要选择

在这里插入图片描述

Parallel Scavenge 收集器 【新】

【 美 /ˈpærəlel/】【美 /ˈskævɪndʒ/】

  1. 多线程收集器,它看上去几乎和 ParNew 都一样,关注点是吞吐量
  2. “Stop The World”
  3. 新生代采用标记-复制算法,老年代采用标记-整理算法。
  4. 这是 JDK1.8 默认收集器

Parallel Old 收集器 【老】

Parallel Scavenge 收集器的老年代版本
在这里插入图片描述

CMS收集器 Concurrent Mark Sweep【老】

  1. HotSpot 虚拟机第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程(基本上)同时工作。CMS收集器是一种以获取最短回收停顿时间为目标的收集器。它非常符合在注重用户体验的应用上使用。

  2. 初始标记、重新标记这两个步骤仍然需要“Stop The World”。

  3. CMS是一款基于“标记-清除”算法实现的收集器

  4. 缺点:
    对 CPU 资源敏感;
    无法处理浮动垃圾;
    它使用的回收算法-“标记-清除”算法会导致收集结束时会有大量空间碎片产生。

在这里插入图片描述

G1 收集器(Garbage-First)【新+老】

  1. 是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足 GC 停顿时间要求的同时,还具备高吞吐量性能特征.

在这里插入图片描述

在这里插入图片描述

垃圾回收器详解

Serial收集器:
这个收集器是一个单线程工作的收集器,但它的“单线程”的意义并不仅仅是说明它只会使用一个处理器或一条收集线程去完成垃圾收集工作,更重要的是强调在它进行垃圾收集时,必须暂停其他所有工作线程(stop the world),直到它收集结束。迄今为止,它依然是HotSpot虚拟机运行在客户端模式下的默认新生代收集器。
在用户的桌面应用场景中,分配给虚拟机管理的内存一般来说不会很大,收集几十兆甚至一两百兆的新生代(仅仅是新生代使用的内存,桌面应用基本上不会再大了),停顿时间完全可以控制在几十毫秒最多一百多毫秒以内,只要不是频繁发生,这点停顿是可以接受的。
ParNew收集器:
ParNew收集器实质上是Serial收集器的多线程并行版本,除了同时使用多条线程进行垃圾收集之外,其余的行为包括Serial收集器可用的所有控制参数、收集算法、Stop The World、对象分配规则、回收策略等都与Serial收集器完全一致,在实现上这两种收集器也共用了相当多的代码。

Parallel Scavenge收集器:
Parallel Scavenge的诸多特性从表面上看和ParNew非常相似,那它有什么特别之处呢?
CMS等收集器的关注点是尽可能地缩短垃圾收集时用户线程的停顿时间,而Parallel Scavenge收集器的目标则是达到一个可控制的吞吐量(Throughput)。

如果虚拟机完成某个任务,用户代码加上垃圾收集总共耗费了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%。
Parallel Scavenge收集器提供了两个参数用于精确控制吞吐量:
控制最大垃圾收集停顿时间的·-XX:MaxGCPauseMillis参数:参数允许的值是一个大于0的毫秒数,收集器将尽力保证内存回收花费的时间不超过用户设定值。不过大家不要异想天开地认为如果把这个参数的值设置得更小一点就能使得系统的垃圾收集速度变得更快,垃圾收集停顿时间缩短是以牺牲吞吐量和新生代空间为代价换取的:系统把新生代调得小一些,收集300MB新生代肯定比收集500MB快,但这也直接导致垃圾收集发生得更频繁,原来10秒收集一次、每次停顿100毫秒,现在变成5秒收集一次、每次停顿70毫秒。停顿时间的确在下降,但吞吐量也降下来了。
直接设置吞吐量大小的-XX:GCTimeRatio参数:参数的值则应当是一个大于0小于100的整数,譬如把此参数设置为19,那允许的最大垃圾收集时间就占总时间的5%(即1/(1+19)),默认值为99,即允许最大1%(即1/(1+99))的垃圾收集时间。
-XX:+UseAdaptiveSizePolicy参数:
虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量。这种调节方式称为垃圾收集的自适应的调节策略(GC Ergonomics)。
手工优化存在困难的话,使用Parallel Scavenge收集器配合自适应调节策略,只需要把基本的内存数据设置好(如-Xmx设置最大堆),然后使用-XX:MaxGCPauseMillis参数(更关注最大停顿时间)或-XX:GCTimeRatio(更关注吞吐量)参数给虚拟机设立一个优化目标,那具体细节参数的调节工作就由虚拟机完成了。自适应调节策略也是Parallel Scavenge收集器区别于ParNew收集器的一个重要特性。
Serial Old收集器:
Serial Old是Serial收集器的老年代版本,它同样是一个单线程收集器,使用标记-整理算法。这个收集器的主要意义也是供客户端模式下的HotSpot虚拟机使用。如果在服务端模式下,它也可能有两种用途:一种是在JDK 5以及之前的版本中与Parallel Scavenge收集器搭配使用,另外一种就是作为CMS收集器发生失败时的后备预案,在并发收集发生Concurrent Mode Failure时使用
Parallel Old收集器:
Parallel Old是Parallel Scavenge收集器的老年代版本,支持多线程并发收集,基于标记-整理算法实现.
Parallel Old收集器出现后,“吞吐量优先”收集器终于有了比较名副其实的搭配组合,在注重吞吐量或者处理器资源较为稀缺的场合,都可以优先考虑Parallel Scavenge加Parallel Old收集器这个组合
CMS收集器:
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。
1)初始标记(CMS initial mark)
初始标记仅仅只是标记一下GCRoots能直接关联到的对象,速度很快;
2)并发标记(CMS concurrent mark)
并发标记阶段就是从GC Roots的直接关联对象开始遍历整个对象图的过程,这个过程耗时较长但是不需要停顿用户线程,可以与垃圾收集线程一起并发运行
3)重新标记(CMS remark)
重新标记阶段则是为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间通常会比初始标记阶段稍长一些,但也远比并发标记阶段的时间短
4)并发清除(CMS concurrent sweep)
并发清除阶段,清理删除掉标记阶段判断的已经死亡的对象,由于不需要移动存活对象,所以这个阶段也是可以与用户线程同时并发的。

在整个过程中耗时最长的并发标记和并发清除阶段中,垃圾收集器线程都可以与用户线程一起工作,所以从总体上来说,CMS收集器的内存回收过程是与用户线程一起并发执行的
以下三个明显的缺点:
1.面向并发设计的程序都对处理器资源比较敏感。在并发阶段,它虽然不会导致用户线程停顿,但却会因为占用了一部分线程(或者说处理器的计算能力)而导致应用程序变慢,降低总吞吐量。
2.由于CMS收集器无法处理“浮动垃圾”(Floating Garbage),有可能出现“Con-currentMode Failure”失败进而导致另一次完全“Stop The World”的Full GC的产生。
在CMS的并发标记和并发清理阶段,用户线程是还在继续运行的,程序在运行自然就还会伴随有新的垃圾对象不断产生,但这一部分垃圾对象是出现在标记过程结束以后,CMS无法在当次收集中处理掉它们,只好留待下一次垃圾收集时再清理掉。这一部分垃圾就称为“浮动垃圾”。
在JDK 5的默认设置下,CMS收集器当老年代使用了68%的空间后就会被激活,-XX:CMSInitiatingOccu-pancyFraction的值来提高CMS的触发百分比,降低内存回收频率,获取更好的性能。到了JDK 6时,CMS收集器的启动阈值就已经默认提升至92%。
但这又会更容易面临另一种风险:要是CMS运行期间预留的内存无法满足程序分配新对象的需要,就会出现一次“并发失败”(Concurrent Mode Failure),这时候虚拟机将不得不启动后备预案:冻结用户线程的执行,临时启用Serial Old收集器来重新进行老年代的垃圾收集,但这样停顿时间就很长了。所以参数-XX:CMSInitiatingOccupancyFraction设置得太高将会很容易导致大量的并发失败产生,性能反而降低,用户应在生产环境中根据实际应用情况来权衡设置。
3.CMS是一款基于“标记-清除”算法实现的收集器,这意味着收集结束时会有大量空间碎片产生。空间碎片过多时,将会给大对象分配带来很大麻烦,往往会出现老年代还有很多剩余空间,但就是无法找到足够大的连续空间来分配当前对象,而不得不提前触发一次Full GC的情况。为了解决这个问题,CMS收集器提供了一个-XX:+UseCMS-CompactAtFullCollection开关参数(默认是开启的,此参数从JDK 9开始废弃),用于在CMS收集器不得不进行Full GC时开启内存碎片的合并整理过程,由于这个内存整理必须移动存活对象,(在Shenandoah和ZGC出现前)是无法并发的。这样空间碎片问题是解决了,但停顿时间又会变长,因此虚拟机设计者们还提供了另外一个参数-XX:CMSFullGCsBefore-Compaction(此参数从JDK 9开始废弃),这个参数的作用是要求CMS收集器在执行过若干次(数量由参数值决定)不整理空间的Full GC之后,下一次进入Full GC前会先进行碎片整理(默认值为0,表示每次进入Full GC时都进行碎片整理)。

Garbage First收集器:
JDK 9发布之日,G1宣告取代Parallel Scavenge加Parallel Old组合,成为服务端模式下的默认垃圾收集器,而CMS则沦落至被声明为不推荐使用(Deprecate)的收集器。
在G1收集器出现之前的所有其他收集器,包括CMS在内,垃圾收集的目标范围要么是整个新生代(Minor GC),要么就是整个老年代(Major GC),再要么就是整个Java堆(Full GC)。而G1跳出了这个樊笼,它可以面向堆内存任何部分来组成回收集(Collection Set,一般简称CSet)进行回收,衡量标准不再是它属于哪个分代,而是哪块内存中存放的垃圾数量最多,回收收益最大,这就是G1收集器的Mixed GC模式。
G1不再坚持固定大小以及固定数量的分代区域划分,而是把连续的Java堆划分为多个大小相等的独立区域(Region),每一个Region都可以根据需要,扮演新生代的Eden空间、Survivor空间,或者老年代空间。收集器能够对扮演不同角色的Region采用不同的策略去处理,这样无论是新创建的对象还是已经存活了一段时间、熬过多次收集的旧对象都能获取很好的收集效果。
Humongous区域,专门用来存储大对象。G1认为只要大小超过了一个Region容量一半的对象即可判定为大对象。每个Region的大小可以通过参数-XX:G1HeapRegionSize设定,取值范围为1MB~32MB,且应为2的N次幂。而对于那些超过了整个Region容量的超级大对象,将会被存放在N个连续的Humongous Region之中,G1的大多数行为都把Humongous Region作为老年代的一部分来进行看待。
G1仍然保留新生代和老年代的概念,但新生代和老年代不再是固定的了,它们都是一系列区域(不需要连续)的动态集合。
G1收集器的运作过程大致可划分为以下四个步骤:
1·初始标记(Initial Marking):仅仅只是标记一下GC Roots能直接关联到的对象,并且修改TAMS指针的值,让下一阶段用户线程并发运行时,能正确地在可用的Region中分配新对象。这个阶段需要停顿线程,但耗时很短,而且是借用进行Minor GC的时候同步完成的,所以G1收集器在这个阶段实际并没有额外的停顿。
3·并发标记(Concurrent Marking):从GC Root开始对堆中对象进行可达性分析,递归扫描整个堆里的对象图,找出要回收的对象,这阶段耗时较长,但可与用户程序并发执行。当对象图扫描完成以后,还要重新处理SATB记录下的在并发时有引用变动的对象。
3·最终标记(Final Marking):对用户线程做另一个短暂的暂停,用于处理并发阶段结束后仍遗留下来的最后那少量的SATB记录。
4·筛选回收(Live Data Counting and Evacuation):负责更新Region的统计数据,对各个Region的回收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划,可以自由选择任意多个Region构成回收集,然后把决定回收的那一部分Region的存活对象复制到空的Region中,再清理掉整个旧Region的全部空间。这里的操作涉及存活对象的移动,是必须暂停用户线程,由多条收集器线程并行完成的。
目前在小内存应用上CMS的表现大概率仍然要会优于G1,而在大内存应用上G1则大多能发挥其优势,这个优劣势的Java堆容量平衡点通常在6GB至8GB之间,当然,以上这些也仅是经验之谈,不同应用需要量体裁衣地实际测试才能得出最合适的结论,随着HotSpot的开发者对G1的不断优化,也会让对比结果继续向G1倾斜。
在这里插入图片描述

垃圾回收器回收步骤

年轻代

串行GC(Serial Copying)/Serial 收集器

1.算法:复制(copying)清理算法

2.操作步骤:
2.1扫描年轻代中所有存活对象
2.2使用Minor GC进行垃圾回收,同时将存活的对象保存到S0/S1区。
2.3在上一次Minor GC的基础上进行S0和S1区的角色交换。
2.4经历过多次Minor GC依然存活的对象晋升到老年代。

3.串行收集器采用单线程方式进行收集,且在 GC 线程工作时,系统不允许应用线程打扰。此时,应用程序进入暂停状态,即 Stop-the-world。Stop-the-world 暂停时间的长短,是衡量一款收集器性能高低的重要指标

并行GC(ParNew)/ParNew 收集器

1.算法:复制(copying)清理算法
2.操作步骤:
并行GC(ParNew)必须结合老年代CMS GC一起使用。因为在年轻代如果发生了Minor GC时,老年代也需要使用CMS GC同时处理。因为并行回收GC并不会做这些。
3.可以看成是 Serial 的多线程版本,采用多个 GC 线程并行收集。多条 GC 线程执行显然比只使用一条 GC 线程执行的效率更高。一般来说,与串行收集器相比,在多处理器环境下工作的并行收集器能够极大地缩短 Stop-the-world 时间。

并行回收GC(Parallel Scavenge)/Parallel Scavenge 收集器

1.算法:复制(copying)清理算法
2.操作步骤
在扫描和复制时均采用多线程方式处理,并行回收GC为空间较大的年轻代回收提供许多优化。
3.和 ParNew 类似,但更注重吞吐率。在 ParNew 的基础上演化而来的 Parallel Scanvenge 收集器被誉为“吞吐量优先”收集器。
Parallel Scanvenge 收集器在 ParNew 的基础上提供了一组参数,用于配置期望的收集时间或吞吐量,然后以此为目标进行收集。通过 VM 选项可以控制吞吐量的大致范围:

-XX:MaxGCPauseMills:设置并行收集器最大暂停时间,单位为ms,用来控制收集对应用程序停顿的影响。
-XX:GCTimeRatio:设置垃圾回收时间占程序运行时间的百分比,用来控制吞吐量。
-XX:UseAdaptiveSizePolicy:自动分代大小调节策略。

但要注意停顿时间与吞吐量这两个目标是相悖的,降低停顿时间的同时也会引起吞吐的降低。因此需要将目标控制在一个合理的范围中。

老年代

串行GC(Serial MSC)/Serial Old 收集器

1.算法:标记-清除-压缩(mark-sweep-compact)/“标记-整理”算法
2.操作步骤:
2.1扫描老年代中还存活的对象,并对这些对象进行标记
2.2遍历整个老年代的内存空间,回收所有未标记的对象内存。
2.3将所有存活对象都集中在一端,然后把所有被回收的对象的内存空间变成一块连续的内存空间。
4.Serial Old 是 Serial 收集器的老年代版本,单线程收集器。这个收集器的主要意义也是在于给 Client 模式下的虚拟机使用。
优缺点:串行执行为单线程,需要暂停应用并耗时较长

并行GC(Parallel Mark Sweep,Parallel Compacting)/Parallel Old 收集器

1.算法:标记-清除-压缩(mark-sweep-compact)/采用“标记-整理”算法。
2.操作步骤:
2.1将老年代内存空间按照线程个数划分为若干个子区域
2.2多个线程并行对各自子区域内存活对象进行标记
2.3多个线程并行清除所有的未标记的对象
2.4多个线程并行将多个存活的对象整理在一起,并将所有的被回收的对象空间整合为一体。
3.Parallel Old 是 Parallel Scanvenge 收集器的老年代版本,多线程收集器。
优缺点:多线程可以缩短应用的暂停时间,因为老年代较大,扫描和标记较费时。

并发GC(Concurrent Mark-Sweep GC,CMS GC)/CMS收集器

1.算法:标记-清除(mark-sweep)
2.操作步骤:
2.1初始标记(STW Initial Mark):
虚拟机暂停正在执行的任务,由根对象扫描出所有的关联对象,并作出标记,此过程只会导致JVM短暂暂停。
2.2并发标记(Concurrent Marking):
恢复所有暂停的线程对象,并对之前标记的对象进行扫描,取得所有跟标记对象有关的对象。
2.3并发预清理(Concurrent Precleaning):
查找所有在并发标记阶段进入老年代的对象。
2.4重新标记(STW Remark):
此阶段会暂停虚拟机,对在并发标记阶段被改变引用或者重新创建的对象进行标记。重新标记阶段则是为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录。这个阶段的停顿时间一般会比初始阶段稍长一些,但远比并发标记的时间短。
2.5并发清理:
恢复所有暂停的应用线程,对所有未标记的垃圾对象进行清理,并且尽量将已经回收的对象的空间拼凑成一个整体,在此阶段收集器线程和应用程序线程并发执行。
2.6并发重置:
重置CMS收集器的数据结构,等待下一次垃圾回收。
3.CMS(Concurrent Mark Swee)收集器是一种以获取最短回收停顿时间为目标的收集器。

CMS 以流水线方式拆分了收集周期,将耗时长的操作单元保持与应用线程并发执行。只将那些必需 STW 才能执行的操作单元单独拎出来,控制这些单元在恰当的时机运行,并能保证仅需短暂的时间就可以完成。这样,在整个收集周期内,只有两次短暂的暂停(初始标记和重新标记),达到了近似并发的目的。
CMS 收集器之所以能够做到并发,根本原因在于采用基于“标记-清除”的算法并对算法过程进行了细粒度的分解。前面已经介绍过“标记-清除”算法将产生大量的内存碎片这对新生代来说是难以接受的,因此新生代的收集器并未提供 CMS 版本

CMS 收集器优点:并发收集,低停顿。
CMS 收集器缺点:
    CMS 收集器对 CPU 资源非常敏感,并发标记与回收线程会与应用程序争抢CPU资源;
    CMS 收集器无法处理浮动垃圾;
    CMS 收集器是基于“标记-清除”算法,该算法的缺点都有,比如容易产生内存碎片

GC策略调整参数

默认情况

在这里插入图片描述

具体搭配情况

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

UseParallelGC和UseParallelOldGC的区别。参考: https://www.cnblogs.com/kelthuzl.
大神的参考链接: https://blog.csdn.net/.

参与评论 您还未登录,请先 登录 后发表或查看评论

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

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页

打赏作者

张紫娃

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值