【JVM系列6】垃圾收集器

简介: 这段时间因为要忙项目,有近2周没有更新文章了,现在项目的前期工作都准备完毕,今天开始进入正轨,也就稍有余力开始学习一些知识。

S6(R42O~E(ZSH$I23D$K@YU.jpg

文章主要总结《深入理解Java虚拟机》的“经典垃圾收集器”一节。


前言


这段时间因为要忙项目,有近2周没有更新文章了,现在项目的前期工作都准备完毕,今天开始进入正轨,也就稍有余力开始学习一些知识。

对于“垃圾收集器“这个知识点,之前也简单提到过,但是一直没有单独拎出来讲,主要是不想去讲太偏八股的东西,也一直认为“垃圾收集器“就是些概念性的内容,在实际工作中不会用到。后来看了《深入理解Java虚拟机》一书,发现“垃圾收集器”在实际项目中还是有它的应用场景,比如你可能需要为你的项目选择合适的垃圾收集器(一般情况下不会),那么就需要对各种垃圾收集器有一个整体的了解。


经典垃圾收集器


在HotSpot虚拟机里面实现了七种作用于不同分代的收集器。

image.gifT18HUY]XQ9K_$1`KG@D6CF9.jpg

如果两个收集器之间存在连线,就说明它们可以搭配使用 ,图中收集器所处的区域,则表示它是属于新生代收集器抑或是老年代收集器。

虽然我们会对各个收集器进行比较,但并非为了挑选一个最好的收集器出来,虽然垃圾收集器的技术在不断进步,但直到现在还没有 最好的收集器出现,更加不存在“万能”的收集器,所以我们选择的只是对具体应用最合适的收集器。


Serial收集器

Serial收集器是最基础、历史最悠久的收集器,是一个单线程工作的收集器,使用 Serial收集器,无论是进行 Minor gc 还是 Full GC ,清理堆空间时,所有的应用线程都会被暂停。进行Full GC时,它还会对老年代空间的对象进行压缩整理。通过 -XX:+UseSerialgGC 标志可以启用 Serial收集器。3

image.gifW5{5G{L4K5OTIV2]B50M6YI.png

对于单核处理器或处理器核心数较少的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程收集效率。Serial收集器对于运行在客户端模式下的虚拟机来说是一个很好的选择。


ParNew收集器

ParNew 收集器实质上是 Serial 收集器的多线程并行版本,除了同时使用多条线程进行垃圾收集之外,其余的行为包括 Serial 收集器可用的所有控制参数、收集算法、Stop The World、对象分配规则、回收策略等都与 Serial 收集器完全一致。

)(271P5}IYE2]@RSABCOZIW.png

ParNew 收集器在单核心处理器的环境中绝对不会有比 Serial 收集器更好的效果,甚至由于存在线程交互的开销,该收集器在通过超线程(Hyper-Threading)技术实现的伪双核处理器环境中都不能百分之百保证超越Serial收集器。是JDK 7之前的遗留系统中首选的新生代收集器。


Pararrel Scavenge收集器

Parallel Scavenge收集器也是一款新生代收集器,基于标记——复制算法实现,能够并行收集的多线程收集器和 ParNew 非常相似。

Parallel Scavenge 收集器的目标则是达到一个可控制的吞吐量(Throughput)。所谓吞吐量就是处理器用于运行用户代码的时间与处理器总消耗时间的比值。如果虚拟机完成某个任务,用户代码加上垃圾收集总共耗费了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%。

Parallel Scavenge 收集器提供了两个参数用于精确控制吞吐量,分别是控制最大垃圾收集停顿时间的 -XX:MaxGCPauseMillis 参数和直接设置吞吐量大小的**-XX:GCTimeRatio** 参数。


Serial Old收集器

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

W5{5G{L4K5OTIV2]B50M6YI.png


Parallel Old收集器

Parallel Old是Parallel Scavenge收集器的老年代版本,支持多线程并发收集,基于标记-整理算法实现。

image.gif_D61Q~)_TV`AK70E%B49N@F.png

在JDK8里面默认垃圾收集器是 UseParallelGC 即 Parallel Scavenge + Parallel Old 。使用 java -XX:+PrintCommandLineFlags  -version 命令可以查看


CMS收集器

CMS 收集器设计的初衷是为了消除 Parallel 收集器和 Serial 收集器 Full gc 周期中的长时间停顿。CMS收集器在 Minor gc 时会暂停所有的应用线程,并以多线程的方式进行垃圾回收。CMS收集器基于标记-清除算法实现的,整个过程分为四个步骤, 整个过程中耗时最长的并发标记和并发清除阶段中,垃圾收集器线程都可以与用户线程一 起工作,所以从总体上来说,CMS收集器的内存回收过程是与用户线程一起并发执行的。

垃圾回收过程如下:

  1. 初始标记(CMS initial mark):初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快;仍然需要“Stop The World”。
  2. 并发标记(CMS concurrent mark):并发标记阶段就是从GC Roots的直接关联对象开始遍历整个对象图的过程,这个过程耗时较长但是不需要停顿用户线程,可以与垃圾收集线程一起并发运行。
  3. 重新标记(CMS remark):重新标记阶段则是为了修正并发标记期间,因为用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间通常会比初始标记阶段稍长一些,但也远比并发标记阶段的时间短。
  4. 并发清除(CMS concurrent sweep):并发清除阶段,清理删除掉标记阶段判断的已经死亡的对象,由于不需要移动存活对象,所以这个阶段也是可以与用户线程同时并发的。

image.gifTNF(Q_M(EE3}$]I]V~6{G0S.png

CMS收集器是一种以获取最短回收停顿时间为目标的收集器。目前很大一部分的Java应用集中在互联网网站或者基于浏览器的 B/S 系统的服务端上,这类应用通常都会较为关注服务的响应速度,希望系统停顿时间尽可能短,以给用户带来良好的交互体验。CMS收集器就非常符合这类应用的需求。


Garbage First收集器(G1)

在G1收集器出现之前的所有其他收集器,包括CMS在内,垃圾收集的目标范围要么为整个新生代(Minor GC),要么就是整个老年代(Major GC),再要么就是整个Java堆(Full GC)。而G1垃圾收集器使用Mixed GC模式可以面向堆内存任何部分来组成回收集(Collection Set,一般简称为Cset)进行回收,衡量标准不再是它属于哪个年代,而是哪块内存中存放的垃圾数最多,回收收益最大。

G1基于Region的堆布局时它能够实现这个目标的关键。虽然G1仍是遵循分代收集理论设计的,但其堆内存的布局与其他收集器有非常明显的差异:G1不再坚持固定大小以及固定数量的分代区域划分,而是把连续的Java堆划分为大小相等的独立区域(Region),且每一个Region都可以根据需要扮演新生代的Eden空间,Survivor空间或者老年代空间。收集器能够对扮演不同角色的Region采用不同的策略去处理,这样无论是新创建的对象还是已经存活一段时间,熬过多次收集的旧对象都能获取很好的收集效果。

Region中还有一类特殊的Humongous区域,专门用来存储大对象。G1认为只要大小超过了一个Region容量一半的对象即可判定为大对象。每个Region的大小可以通过参数-XX:G1HeapRegionSize设定,取值范围为1MB~32MB,且为2的N次幂。而对于那些超过整个Region容量的超级大对象,将会被存放N个连续的Humongous Region中,G1的大多数行为都把HumonGous Region作为老年代的一部分来进行看待。

15411[FKICJOAY`$I]374EA.png

虽然G1仍然保留新生代和老年代的概念,但新生代和老年代不再是固定的了,它们都是一系列无序连续区域的动态集合。G1收集器之所以能建立可预测的停顿时间模型,是因为它将Region作为单词回收的最小单元,即每次收集到的内存空间都是Region大小的整数倍,这样可以有计划地避免在整个Java堆中进行全区域的垃圾收集。

更具体的思路为让G1收集器去跟踪各个Region中的垃圾堆积的"价值"大小,价值即回收所获得的空间大小以及回收所需时间的经验值,然后再后台维护一个有限级列表,每次根据用户设定的收集停顿时间(通过-XX:MaxGCPauseMillis指定,默认值为200毫秒),优先处理回收价值收益最大的Region,这也是"Garbage First"名字的由来。这种使用Region划分内存空间,以及具有优先级的区域回收方式保证了G1收集器在有限的时间内获取尽可能高的收集效率。

收集器的运作过程:

  1. 初始标记(Initial Marking):仅仅只是标记一下GC Roots能直接关联到的对象,并且修改TAMS指针的值,让下一个阶段用户线程并发运行时,能正确地在可用的Region中分配新对象。这个阶段需要停顿线程,但耗时比较短,而且是借用进行Minor GC的时候同步完成的,所以G1收集器在这个阶段实际并没有额外的停顿。
  2. 并发标记(Concurrent Marking):从GC Roots开始对堆中对象进行可达性分析,递归扫描整个堆里的对象图,找出要回收的对象,这阶段耗时比较长,但可与用户程序并发执行。当对象图扫描完成以后,还要重新处理SATB记录下的在并发时有引用变动的对象。
  3. 最终标记(Final Marking):对用户线程做另一个短暂的暂停,用于处理并发阶段结束后仍遗留下来的最后那少量的SATB记录。
  4. 筛选回收(Live Data Counting and Evacuation): 负责更新Region的统计数据,对各个Region的回收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划,可以自由选择任意多个Region构成回收集,然后把决定回收的那一部分Region的存活对象复制到空的Region中,再清理掉整个旧Region的全部空间。这里的操作涉及存活对象的移动,是必须暂停用户线程,由多条收集器线程并行完成的。

image.gifDWR6JYN(_38A4ZU@EDC2~BK.png


垃圾收集器对比


HotSpot VM中的垃圾回收器,以及适用场景:

image.gifDKTE93LB_4$Q{B6_LS37[4C.png

下面是另外一个网友给出的各垃圾收集器对比:

AYSC4F0O}DB768SO4SW6@7G.jpg


总结


简单概括上面的知识:

  • 新生代用“复制算法”,老年代基本用“标记-整理”算法,有的也用“标记-清除”算法(新生代因为有surive区域,所以肯定使用的“复制算法”,老年代不可能划分成2个区域,所以肯定不会使用“复制算法”);
  • 单线程垃圾回收器:Serial、Serial Old;
  • 多线程垃圾回收器:ParNew、Parallel Old、Pararrel Scavenge和G1;
  • 适用新生代的垃圾回收器:Serial、ParNew、Pararrel Scavenge和G1;
  • 适用老年代的垃圾回收器:Serial Old、Parallel Old和G1。
相关文章
|
2月前
|
存储 监控 算法
JVM调优深度剖析:内存模型、垃圾收集、工具与实战
【10月更文挑战第9天】在Java开发领域,Java虚拟机(JVM)的性能调优是构建高性能、高并发系统不可或缺的一部分。作为一名资深架构师,深入理解JVM的内存模型、垃圾收集机制、调优工具及其实现原理,对于提升系统的整体性能和稳定性至关重要。本文将深入探讨这些内容,并提供针对单机几十万并发系统的JVM调优策略和Java代码示例。
57 2
|
4月前
|
存储 算法 Java
JVM自动内存管理之垃圾收集算法
文章概述了JVM内存管理和垃圾收集的基本概念,提供一个关于JVM内存管理和垃圾收集的基础理解框架。
JVM自动内存管理之垃圾收集算法
|
4月前
|
存储 算法 Java
JVM组成结构详解:类加载、运行时数据区、执行引擎与垃圾收集器的协同工作
【8月更文挑战第25天】Java虚拟机(JVM)是Java平台的核心,它使Java程序能在任何支持JVM的平台上运行。JVM包含复杂的结构,如类加载子系统、运行时数据区、执行引擎、本地库接口和垃圾收集器。例如,当运行含有第三方库的程序时,类加载子系统会加载必要的.class文件;运行时数据区管理程序数据,如对象实例存储在堆中;执行引擎执行字节码;本地库接口允许Java调用本地应用程序;垃圾收集器则负责清理不再使用的对象,防止内存泄漏。这些组件协同工作,确保了Java程序的高效运行。
32 3
|
4月前
|
C# UED 开发者
WPF打印功能实现秘籍:从页面到纸张,带你玩转WPF打印技术大揭秘!
【8月更文挑战第31天】在WPF应用开发中,打印功能至关重要,不仅能提升用户体验,还增强了应用的实用性。本文介绍WPF打印的基础概念与实现方法,涵盖页面元素打印、打印机设置及打印预览。通过具体案例,展示了如何利用`PrintDialog`和`PrintDocument`控件添加打印支持,并使用`PrinterSettings`类进行配置,最后通过`PrintPreviewWindow`实现打印预览功能。
442 0
|
4月前
|
C# UED 开发者
WPF动画大揭秘:掌握动画技巧,让你的界面动起来,告别枯燥与乏味!
【8月更文挑战第31天】在WPF应用开发中,动画能显著提升用户体验,使其更加生动有趣。本文将介绍WPF动画的基础知识和实现方法,包括平移、缩放、旋转等常见类型,并通过示例代码展示如何使用`DoubleAnimation`创建平移动画。此外,还将介绍动画触发器的使用,帮助开发者更好地控制动画效果,提升应用的吸引力。
219 0
|
4月前
|
算法 Java 程序员
【JVM的秘密花园】揭秘垃圾收集器的神秘面纱!
【8月更文挑战第25天】在Java虚拟机(JVM)中,垃圾收集(GC)自动管理内存,回收未使用的对象以避免内存泄漏和性能下降。本文深入介绍了JVM中的GC算法,包括串行、并行、CMS及G1等类型及其工作原理。选择合适的GC策略至关重要:小型应用适合串行收集器;大型应用或多核CPU环境推荐并行收集器或CMS;需减少停顿时间时,CMS是好选择;G1适用于大堆且对停顿时间敏感的应用。理解这些能帮助开发者优化程序性能和稳定性。
37 0
|
4月前
|
算法 Java
JVM自动内存管理之垃圾收集器
这篇文章是关于Java虚拟机(JVM)自动内存管理中的垃圾收集器的详细介绍。
|
5月前
|
监控 算法 Java
深入理解Java虚拟机:垃圾收集机制的演变与最佳实践
【7月更文挑战第14天】本文将带领读者穿梭于JVM的心脏——垃圾收集器,探索其设计哲学、实现原理和性能调优。我们将从早期简单的收集算法出发,逐步深入到现代高效的垃圾收集策略,并分享一些实用的调优技巧,帮助开发者在编写和维护Java应用时做出明智的决策。
58 3
|
5月前
|
算法 Java
Java面试题:列举并解释JVM中常见的垃圾收集器,并比较它们的优缺点
Java面试题:列举并解释JVM中常见的垃圾收集器,并比较它们的优缺点
107 3
|
5月前
|
Java UED
Java面试题:描述JVM中垃圾收集的Stop-The-World现象及其影响
Java面试题:描述JVM中垃圾收集的Stop-The-World现象及其影响
63 1