深入理解JVM--第三章--垃圾收集器

本文是基于周志明的《深入理解Java虚拟机》

如果说收集算法是内存回收的方法论,垃圾收集器就是内存回收的具体实现。Java虚拟机规范中对垃圾收集器应该如何实现并没有任何规定,因此不同的厂商、不同版本的虚拟机所提供的垃圾收集器都可能会有很大的差别,并且一般都会提供参数供用户根据自己的应用特点和要求组合出各个年代所使用的收集器。

图展示了7种作用于不同分代的收集器(包括JDK 1.6_Update14后引入的Early Access版G1收集器),如果两个收集器之间存在连线,就说明它们可以搭配使用。

在介绍这些收集器各自的特性之前,我们先来明确一个观点:虽然我们是在对各个收集器进行比较,但并非为了挑选一个最好的收集器出来。因为直到现在为止还没有最好的收集器出现,更加没有万能的收集器,所以我们选择的只是对具体应用最合适的收集器。这点不需要多加解释就能证明:如果有一种放之四海皆准、任何场景下都适用的完美收集器存在,那HotSpot虚拟机就没必要实现那么多不同的收集器了。

--------------------------新生代收集器------------------------

1、Serial收集器:最基本、发展历史最悠久的收集器

适用:看上去没什么用,但实际上到现在为止,它依然是虚拟机运行在Client模式下的默认新生代收集器

特点:

1).单线程的收集器,说明它只会使用一个CPU或一条收集线程去完成垃圾收集工作

2).在它进行垃圾收集时,必须暂停其他所有的工作线程(Sun将这件事情称之为“Stop The World”),直到它收集结束。这项工作实际上是由虚拟机在后台自动发起和自动完成的,在用户不可见的情况下把用户的正常工作的线程全部停掉,这对很多应用来说都是难以接受的。

收集算法:采用复制算法

优点:简单而高效(与其他收集器的单线程比),对于限定单个CPU的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程收集效率。在用户的桌面应用场景中,分配给虚拟机管理的内存一般来说不会很大,收集几十兆甚至一两百兆的新生代(仅仅是新生代使用的内存,桌面应用基本上不会再大了),停顿时间完全可以控制在几十毫秒最多一百多毫秒以内,只要不是频繁发生,这点停顿是可以接受的。所以,Serial收集器对于运行在Client模式下的虚拟机来说是一个很好的选择。

缺点:GC时暂停线程带给用户不良体验

搭配:CMS 或Serial Old(MSC)

-----------------------------------------------------------------------------------------------------------

2 ParNew收集器

ParNew收集器其实就是Serial收集器的多线程版本,除了使用多条线程进行垃圾收集之外,其余行为都与Serial收集器完全一样,实现上这两种收集器也共用了相当多的代码。

适用:运行在Server模式下的虚拟机中的新生代

特点:

1).多线程GC(并行):ParNew是Serial的多线程版本,两者共用了许多代码。

2).在GC时暂停所有用户线程

算法:采用复制算法

优点:高效

缺点:GC时暂停线程带给用户不良体验,单线程下效果不一定优于Serial

搭配:CMS 或Serial Old(MSC)

ParNew收集器有一个与性能无关但很重要的原因是,除了Serial收集器外,目前只有它能与CMS收集器配合工作。在JDK 1.5以后使用CMS来收集老年代的时候,新生代只能选择ParNew或Serial收集器中的一个。

---------------------------------------------------------------------------------------------------------

3、Parallel Scavenge收集器

适用:新生代收集器,在后台运算而不需要太多交互的任务。

特点: 1.多线程GC(并行)

2.在GC时暂停所有用户线程

与其他收集器的不同:

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

吞吐量:运行代码时间/(运行用户代码时间+垃圾收集时间)

停顿时间越短就越适合需要与用户交互的程序,良好的响应速度能提升用户的体验;而高吞吐量则可以最高效率地利用CPU时间,尽快地完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务。

2).Parallel Scavenge可采用GC自适应的调节策略(这是与另外两一个重要的区别)

参数:用于精确控制吞吐量

-XX:MaxGCPauseMillis 最大垃圾收集停顿时间

-XX:GCTimeRatio  垃圾收集时间与运行用户代码时间的比例=垃圾收集时间/运行用户代码时间,相当于是吞吐量的倒数。

实现:降低GC停顿时间:牺牲吞吐量和新生代空间(减小新生代空间,GC频率变大,吞吐量降低)

GC自适应的调节策略 

-XX:+UseAdaptiveSizePolicy 使用自适应的调节策略 即不需要指定新生代的大小,Eden与Surivior的比例,晋升老年代的年龄等细节参数,虚拟机自动根据根据当前系统的状态动态调整这些参数以提供最合适的停顿时间或最大的吞吐量。

算法:采用复制算法

优点:高效

搭配:Parallel Old或Serial Old(MSC)

---------------------------------------------------------------------------------------------------------

-------------------------老年代收集器----------------------------

4、Serial Old收集器

适用

1).运行在Client模式下的虚拟机中的老年代

2).在Server模式下,它主要还有两大用途

①.与Parallel Scavenge搭配

②.作为CMS收集器的后备预案,在并发收集发生Concurrent Mode Failure的时候使用

特点: 1.单线程GC,Serial收集器的老年代版本

2.在GC时暂停所有用户线程

算法:采用标记-整理算法

优点:简单,高效

缺点:GC时暂停线程带给用户不良体验

搭配:Serial Old(MSC)或ParNew

-----------------------------------------------------------------------------------------------------

5、Parallel Old收集器

适用:运行在Server模式下的虚拟机中的新生代.在注重吞吐量及CPU资源敏感的场合,都可以优先考虑Parallel Scavenge加Parallel Old收集器。

特点

1).多线程GC(并行):Parallel Scavenge的老年代版本

2).在GC时暂停所有用户线程

3).这个收集器是在JDK 1.6中才开始提供的

算法:采用标记-整理算法

优点:高效

缺点:GC时暂停线程带给用户不良体验,单线程下效果不一定优于Serial

搭配:Parallel Scavenge

-------------------------------------------------------------------------------------

6、CMS(Concurrent Mark Sweep)收集器:Hotspot上第一个真正意义上的并发收集器。

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

适用:运行在Server模式下的虚拟机中的老年代,适合对响应时间要求高的应用。

算法:采用“标记-清除”算法

特点: 多线程 并发

过程:

1).初始标记:暂停用户线程,标记GC Roots能直接关联的对象,速度很快

2).并发标记:用户线程与标记线程并发,进行GC Roots Tracing的过程

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

4).并发清除:用户线程与清除线程并发。

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

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

通过过图3-10可以比较清楚地看到CMS收集器的运作步骤中并发和需要停顿的时间。

优点:并发收集、低停顿--由于耗时最长的并发标记和并发清除阶段都与用户线程并行工作,故系统停顿时间极短。

缺点:

1).对CPU资源非常敏感。

原因:面向并发设计的程序都对CPU资源比较敏感。并发时,因为占用了一部分线程(或者说CPU资源)而导致应用程序变慢,总吞吐量会降低,应用程序会变慢,当CPU数不足时,尤其明显。

解决:增量式并发收集器(i-CMS):在并发标记、清除时让GC线程与用户线程交替运行,以降低GC线程独占CPU的时间。当GC时间将变长时,效果一般,被丢弃使用。

2).无法处理浮动垃圾,可能出现“Concurrent Mode "Failure"失败而导致另一次Full GC的产生。

浮动垃圾:在并发清除阶段,用户线程仍在运行,此时产生的垃圾无法在该次收集中处理。

同时由于要保证并发,就必须预留内存给用户线程使用,因此CMS无法等到老年代几乎完全填满时再进行收集。JDK 1.5中CMS默认当老年代被使用68%时被激发。1.6中为92%。

当CMS运行期间预留的内存无法满足程序需要,就会出现一次“Concurrent Mode "Failure"失败,这时虚拟机将启动后备预案:临时使用Serial Old收集器来重新进行老年代垃圾收集,这样停顿时间就会很长。

3).产生空间碎片,影响大对象的分配。

这是由于该收集器是由“标记-清除”算法实现的所引起的。所以往往存在有很大空间剩余,当无法找到足够大的连续空间来分配当前对象,不得不提前出发一次Full GC。

解决:

1.-XX:+UseCMSCompactFullCollection 开关参数(默认开启)用于当CMS要进行Full GC时开启内存碎片的合并整理过程,该过程不能并发,故停顿时间变长。

2.-XX:CMSFullGCsBeforeCompaction 用于设置执行多少次不压缩的Full GC后跟着来一次带压缩的Full GC。默认为0,表示每次进入Full GC时都进行碎片整理。

搭配:Serial或ParNew

-----------------------------------------------------------------------------------------

---------------------------新生代和老年代均适用---------------------

7、G1收集器

适用:面向服务端应用,适用于新生代和老年代。当前收集器技术发展的最前沿成果

特点:

1.并行+并发。可充分利用CPU资源

2.分代收集。

3.空间整合。 G1从整体看是”标记-整理“算法,从局部(两个Region之间)看,是”复制“算法。 不会产生空间碎片。

4.可预测的停顿。建立可预测的态度时间模型,能让使用者明确指定在一个长度为M毫秒的时间内,消耗在垃圾收集的时间不得超过N毫秒,这几乎已经是实时Java(RTSJ)的垃圾收集器的特征了。

Garbage First名称的由来  

G1收集器可以实现在基本不牺牲吞吐量的前提下完成低停顿的内存回收,这是由于它能够极力地避免全区域的垃圾收集。G1将内存划分为Region,跟踪各个Region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region。

难点:虽然内存分为Region,但垃圾收集不能真的以Region为单位进行,因为Region不可能是孤立的,存在某个对象被多个Region的引用,那在做可达性判断确定对象是否存活时,是否需要扫描整个堆空间呢?注意:此问题在所有的收集器中都存在(如存在新生代与老年代之间的引用)。

解决:1.使用Remembered Set来避免圈堆扫描。

过程:G1中每个Region都有一个与之对应的Remembered Set,虚拟机发现程序在对Reference类型的数据进行写操作是,会产生一个Write Barrier暂时中断操作,检查Reference类型引用的对象是否处于不同的Region(在分代的例子中就是检查是否老年代的对象引用了新生代中的对象),如果是,便通过CardTable把相关引用信息记录到被引用对象所属的Region的Remembered Set中。当进行内存回收时,在GC根节点的枚举范围中加入Remembered Set即可保证不对全堆扫描也不会有遗漏。

内存布局:G1的堆内存布局与其他收集器不同,G1将整个堆内存空间划分为多个大小相等的Region,虽然仍然有新生代和老年代的概念,但是新生代和老年代不再是物理隔离的,他们都是一部分Region(不需要连续)的集合。

过程(与CMS相似)

1.初始标记:暂停用户线程,标记GC Roots能直接关联的对象

2.并发标记:用户线程与标记线程并发,进行GC Roots的Trace

3.最终标记修正并发标记阶段,因用户线程继续运行而导致标记产生变动的那一部分对象的标记记录。

4.筛选回收:

算法: 全局标记-整理+局部复制算法

优点:高效,停顿时间可控、可预测

8、GC 相关参数总结

1. 与串行回收器相关的参数

-XX:+UseSerialGC:在新生代和老年代使用串行回收器。

-XX:+SuivivorRatio:设置 eden 区大小和 survivor 区大小的比例。

-XX:+PretenureSizeThreshold:设置大对象直接进入老年代的阈值。当对象的大小超过这个值时,将直接在老年代分配。

-XX:MaxTenuringThreshold:设置对象进入老年代的年龄的最大值。每一次 Minor GC 后,对象年龄就加 1。任何大于这个年龄的对象,一定会进入老年代。

2. 与并行 GC 相关的参数

-XX:+UseParNewGC: 在新生代使用并行收集器。

-XX:+UseParallelOldGC: 老年代使用并行回收收集器。

-XX:ParallelGCThreads:设置用于垃圾回收的线程数。通常情况下可以和 CPU 数量相等。但在 CPU 数量比较多的情况下,设置相对较小的数值也是合理的。

-XX:MaxGCPauseMills:设置最大垃圾收集停顿时间。它的值是一个大于 0 的整数。收集器在工作时,会调整 Java 堆大小或者其他一些参数,尽可能地把停顿时间控制在 MaxGCPauseMills 以内。

-XX:GCTimeRatio:设置吞吐量大小,它的值是一个 0-100 之间的整数。假设 GCTimeRatio 的值为 n,那么系统将花费不超过 1/(1+n) 的时间用于垃圾收集。

-XX:+UseAdaptiveSizePolicy:打开自适应 GC 策略。在这种模式下,新生代的大小,eden 和 survivor 的比例、晋升老年代的对象年龄等参数会被自动调整,以达到在堆大小、吞吐量和停顿时间之间的平衡点。

3. 与 CMS 回收器相关的参数

-XX:+UseConcMarkSweepGC: 新生代使用并行收集器,老年代使用 CMS+串行收集器。

-XX:+ParallelCMSThreads: 设定 CMS 的线程数量。

-XX:+CMSInitiatingOccupancyFraction:设置 CMS 收集器在老年代空间被使用多少后触发,默认为 68%。

-XX:+UseFullGCsBeforeCompaction:设定进行多少次 CMS 垃圾回收后,进行一次内存压缩。

-XX:+CMSClassUnloadingEnabled:允许对类元数据进行回收。

-XX:+CMSParallelRemarkEndable:启用并行重标记。

-XX:CMSInitatingPermOccupancyFraction:当永久区占用率达到这一百分比后,启动 CMS 回收 (前提是-XX:+CMSClassUnloadingEnabled 激活了)。

-XX:UseCMSInitatingOccupancyOnly:表示只在到达阈值的时候,才进行 CMS 回收。

-XX:+CMSIncrementalMode:使用增量模式,比较适合单 CPU。

4. 与 G1 回收器相关的参数

-XX:+UseG1GC:使用 G1 回收器。

-XX:+UnlockExperimentalVMOptions:允许使用实验性参数。

-XX:+MaxGCPauseMills:设置最大垃圾收集停顿时间。

-XX:+GCPauseIntervalMills:设置停顿间隔时间。

5. 其他参数

-XX:+DisableExplicitGC: 禁用显示 GC。

时间: 2024-08-13 10:09:34

深入理解JVM--第三章--垃圾收集器的相关文章

第三章 垃圾收集器和内存分配策略

第三章 垃圾收集器和内存分配策略 对象已死吗 引用计算方法 可达性分析算法 通过一些列的GC roots 对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径成为引用链,当一个对象到GC roots 没有任何引用链的则证明对象不可用的 虚拟机栈中的引用的对象 方法区中类静态属性引用的对象 方法去区中常量引用的对象 本地方法栈中JNI引用的对象 生存还是死亡 一次筛选,筛选是否有必要执行 finalize()方法 没有覆盖或者finalize()已经被调用过  视为没必要执行 放入一个F-Qu

《深入理解JAVA虚拟机》----------第三章 垃圾收集器与内存分配策略,读后感(中)

1.垃圾收集器 1.1 Serial收集器 这个收集器是一个单线程的收集器,它在进行垃圾收集时,必须暂停其他所有的工作线程. 它是虚拟机运行在Client模式下的默认新生代收集器,它简单而高效. 1.2 ParNew收集器 其实就是Serial收集器的多线程版本,目前只有它能与CMS收集器配合工作. 原文地址:https://www.cnblogs.com/technologykai/p/10622586.html

第三章 垃圾收集器与内存分配策略

书中笔记: 也许并不会死: 要宣告回收一个对象死亡,至少要经历两次标记过程: 当可达性分析发现一个对象不可达的时候,将标记第一次并进行筛选,筛选的条件是此对象是否有必要执行finalize()方法,当对象没有覆盖finalize或者已被调用过,则虚拟机认为此对象没必要执行finalize,  如果判断有必要执行,则此对象将会被放入一个F-Queue队列中,之后会被一个优先级比较低的Finalizer线程去调用,但是并不会等待他执行完毕,因为此对象的finalize并不可靠,可能会死循环之类的,如

【深入理解JVM】:HotSpot垃圾收集器

相关概念 并发和并行 这两个名词都是并发编程中的概念,在谈论垃圾收集器的上下文语境中,它们可以解释如下. 并行(Parallel):指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态. 并发(Concurrent):指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),用户程序在继续运行,而垃圾收集程序运行于另一个CPU上. Minor GC 和 Full GC 新生代GC(Minor GC):指发生在新生代的垃圾收集动作,因为Java对象大多都具备朝生夕灭的特性,所以M

第三章垃圾收集器与内存分配策略

3.2对象死亡的判断方法 3.2.1引用计数法 给对象添加一个引用计数器,每当一个地方引用它就+1,引用失效就-1,当计数器为0时就表示对象已经死亡. 缺点是无法解决循环引用问题 3.2.2可达性分析 将GC root作为根节点向下遍历,无法遍历到的对象(GC Root到这个对象不可达)就表示该对象已经死亡. 3.2.3对象的自救 已经死亡的对象会被第一次标记,然后进行筛选,筛选出是否有必要执行finalize()方法,如果对象有并且是第一次被调用那么对像将被放在F-Queue队列中,等待虚拟机

【OPENGL】第三章 着色器基础(一)

在这一章,我们会学习什么是着色器(Shader),什么是着色器语言(OpenGL Shading Language-GLSL),以及着色器怎么和OpenGL程序交互. 首先我们先来看看什么叫着色器. Shader(着色器)是用来实现图像渲染的,用来替代固定渲染管线的可编程程序. 着色器替代了传统的固定渲染管线,可以实现3D图形学计算中的相关计算,由于其可编程性,可以实现各种各样的图像效果而不用受显卡的固定渲染管线限制.这极大的提高了图像的画质. 在上一篇文章( http://www.cnblog

GC入门指南(三)----垃圾收集器类型

这篇文章我们来学习下所有可用的垃圾收集器类型.java目前有四种类型的垃圾收集器: 1.串行垃圾收集器(Serial Garbage Collector): 2.并行垃圾收集器(Parallel Garbage Collector): 3.CMS垃圾收集器(CMS Garbage Collector); 4.G1垃圾收集器(G1 Garbage Collector); 每种类型都有其优点和缺点,最重要的是我们开发者可以通过jvm参数为JVM选择不同的垃圾收集器.每种垃圾收集器关注点都不同,其带来

[深入理解JVM虚拟机]第3章-垃圾收集器、内存分配策略

垃圾收集器 判断对象是否需存活 回收堆 判断对象是否存活: 方法一:引用计数法.对象被引用一次就+1,当为0时回收对象.缺点:无法解决循环引用问题. 方法二:可达性分析算法.记录当前对象是否有和GC Roots中对象的引用链.(其中,可以作为GCRoots对象的有:虚拟机栈中引用的对象.方法去中类静态属性引用的对象.方法区中常量引用的对象.本地方法栈中引用的对象.) 不可达对象并不是一定被垃圾收集的,当这个对象有必要执行finalize()并finalize里自己和某个对象建立关联,即可在第二次

深入理解JVM实战(三)——垃圾收集策略详解

极客头条用户请点击"阅读原文",阅读排版后原文 Java虚拟机的内存模型分为五个部分,分别是:程序计数器.Java虚拟机栈.本地方法栈.堆.方法区. 这五个区域既然是存储空间,那么为了避免Java虚拟机在运行期间内存存满的情况,就必须得有一个垃圾收集者的角色,不定期地回收一些无效内存,以保障Java虚拟机能够健康地持续运行. 这个垃圾收集者就是平常我们所说的"垃圾收集器",那么垃圾收集器在何时清扫内存?清扫哪些数据?这就是接下来我们要解决的问题. 程序计数器.Jav