JVM学习笔记-第三章-垃圾收集器与内存分配策略

tips:对于3.4之前的章节可见博客:https://blog.csdn.net/sanhewuyang/article/details/95380620


3.5 经典垃圾收集器

3.5.1 Serial收集器

这个收集器是一个单线程工作的收集器,但它的单线程的意义并不仅仅是说明他只会使用一个处理器或一条收集线程去完成垃圾收集工作,更重要对的是强调在它进行垃圾收集时,必须暂停其他所有工作线程,直到它收集结束。

目前已经老无可用,但有着优于其他收集器的地方:简单而高效

3.5.2 ParNew收集器

ParNew收集器实质上是Serial收集器的多线程并行版本。因为它是除了Serial收集器之外,目前唯一可以与CMS收集器配合工作的收集器,所以在JDK7之前的遗留系统中被作为首选的新生代收集器

CMS收集器是HotSpot虚拟机中第一款真正意义上支持并发的垃圾收集器,首次实现了让垃圾收集线程与用户线程同时工作。但是当选用CMS作为老年代收集器时,新生代收集器只能选择使用Serial收集器或者ParNew收集器

随着垃圾收集器技术的不断改进,G1收集器带着CMS继承者和代替者的光环登场。G1收集器是一个面向全堆的收集器,不需要其他新生代收集器的配合工作

3.5.3 Parallel Scavenge 收集器

Parallel Scavenge收集器也是一款新生代收集器,同样是基于标记-复制算法实现的收集器,也可以并行收集的多线程收集器。它的特点是它的关注点与其他收集器不同。CMS等收集器的关注点是尽可能地缩短垃圾收集时用户线程的停顿时间,而Parallel Scavenge收集器的目标则是达到一个可控制的吞吐量。

$$

吞吐量=\frac{运行用户代码时间}{运行用户代码时间+运行垃圾收集时间}

$$

提供了两个参数用于精确控制吞吐量:

-XX:MaxGCPauseMillis 参数控制最大垃圾搜集停顿时间,允许的值是一个大于0的毫秒数。收集器将尽力保证内存回收花费的时间不超过用户的设定值。但是设定过分小的值并不能起到加快回收花费的速度的作用。

-XX:GCTimeRatio 参数直接设置吞吐量大小,允许的值是一个大于0小于100的整数。也就是垃圾收集时间占总时间的比率。相当于吞吐量的倒数。

Parallel Scavenge 收集器还有一个参数:-XX:+UseAdaptiveSizePolicy 这是一个开关参数,当这个参数被激活以后,就不需要人工指定新生代的大小,Eden与Survivor区的比例等等。虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数。

3.5.4 Serial Old 收集器

Serial Old 是 Serial收集器的老年代版本,同样是一个单线程收集器,使用标记-整理算法。可能有两种用途:1. 在JDK5以及之前的版本中与Parallel Scavenge收集器搭配使用 2. 作为CMS收集器发生失败时的后备预案。

3.5.5 Parallel Old 收集器

Parallel Old 是 Parallel Scavenge收集器的老年代版本,支持多线程并发收集,基于标记-整理算法实现,从JDK6版本开始提供。在注重吞吐量或者处理器资源较为稀缺的场合,都可以优先考虑Parallel Scavenge加Parallel Old收集器这个组合。

3.5.6 CMS 收集器

CMS收集器是一种以获取最短回收停顿时间为目标的收集器,基于标记-清除算法实现。整个运作过程分为4步:

步骤名称 行为
初始标记(CMS initial mark) 标记一下GC Roots能直接关联到的对象,需要Stop The World
并发标记(CMS concurrent mark) 从GC Roots的直接关联对象开始遍历整个对象图的过程,可以与垃圾收集线程一起并发运行
重新标记(CMS remark) 修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,需要Stop the World
并发清楚(CMS concurrent sweep) 清理删除掉标记阶段判断的已经死亡的对象,可以与用户线程同时并发完成

CMS收集器存在三个缺点:

1.CMS收集器对处理器资源非常敏感,默认启动的回收线程数为(处理器核心数量+3)/ 4。在并发阶段会因为占用了一部分线程而导致应用程序变慢,降低总吞吐量。

为了缓解这种情况虚拟机提供了“增量式并发收集器”(Incremental Concurrent Mark Sweep/i-CMS)作用是在并发标记、清理的时候让收集器线程、用户线程交替运行,尽量减少垃圾收集器线程的独占资源的时间,这样整个垃圾收集的过程会更长,但是对用户程序的影响就会显得较少一些,直观感受是速度变慢的时间更多了,但速度下降幅度就没有那么明显。效果一般从jdk7开始被声明为deprecated ,从JDK9发布后被完全废弃

2.由于CMS收集器无法处理“浮动垃圾”(Floating Garbage),有可能出现"Concurrent Mode Failure" 失败进而导致另一完全"Stop The World"的Full GC的产生。

可以适当调高参数-XX:CMSInitiatingOccu-pancyFraction的值来提高CMS的触发百分比,降低内存回收频率,获得更好的性能。如果设置的太高将会很容易导致大量的并发失败产生,性能反而降低

3.由于基于标记-清除算法,可能在收集结束时会有大量的空间碎片产生

通过调节:-XX:+UseCMSCompactAtFullCollection开关参数,默认是开启的,从jdk9开始废弃

*** -XX:CMSFullGCsBeforeCompaction 默认值是0,表示每次进入Full GC时都进行碎片整理***

3.5.7 Garbage First 收集器

Garbage First 收集器,简称 G1收集器,开创了收集器面向局部收集的设计思路和基于Region的内存布局形式。是一款主要面向服务端应用的垃圾收集器。可以面向堆内存的任何部分来组成回收集,衡量的标准不再是它属于哪个分代,而是哪块内存中存放的垃圾数量最多,回收收益最大。G1开创的基于Region的堆内存布局是它能够实现这个目标的关键,G1不再坚持固定大小以及固定数量的分代区域划分,而是把连续的java堆划分为多个大小相等的独立区域(Region),每一个Region都可以根据需要,扮演新生代的Eden空间、Survivor空间或者老年代空间。

Region中还有一类特殊的Humongous区域,专门用来存储大对象。G1认为只要大小超过了Region容量一半的对象就可以判定为大对象。每个Region的大小可以通过参数-XX:G1HeapRegionSize设定,取值范围为1MB~32MB。

G1收集器之所以可以建立可预测的停顿时间模型,是因为它将Region作为单次回收的最小单元,即每次收集到的内存空间都是Region大小的整数倍,这样可以有计划地避免在整个JAVA堆中进行全区域的垃圾收集。更具体的思路是让G1收集器区跟踪各个Region里面的垃圾堆积的价值大小,价值即回收所获得的空间大小以及回收所需要的时间的经验值,然后在后台 维护一个优先级列表,每次根据用户设定允许的收集停顿时间(-XX:MaxGCPauseMillis)优先处理回收价值收益最大的那些Region。

G1的记忆集在存储结构的本质上是一种哈希表,Key是别的Region的起始地址,Value是一个集合,里面存储的元素是卡表的索引号。G1收集器通过原始快照(SATB)算法实现了保证其不能打破原本的对象图结构的目的。

G1收集器运作过程大致分为四个步骤:

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

G1收集器从整体来看是基于标记-整理算法实现的,从局部来看是基于标记-复制算法来实现的。CMS的写屏障实现是直接的同步操作,G1收集的写屏障的实现是类似于消息队列的结构,将要做的操作存在队列中,异步进行处理。


3.6 低延迟垃圾收集器

3.6.1 Shenandoah收集器

Shenandoah收集器是一款只有OpenJDK才会包含的。与G1收集器相比,它们两者有着相似的堆内存布局,在初始标记、并发标记等许多阶段的处理思路上都高度一致。但是在管理内存堆方面,与G1收集器至少有三个方面的明显的不同之处:

  1. 支持并发的整理算法:G1的回收阶段是可以多线程并行的,但不能与用户线程并发。Shenandoah后面会讲到。
  2. Shenandoah收集器默认不使用分代收集。
  3. Shenandoah摒弃了在G1中耗费大量内存和计算资源去维护的记忆集,改名为“连接矩阵”(Connection Matrix)的全局数据结构来记录跨Region的引用关系。降低了处理跨代指针的记忆集维护消耗,也降低了伪共享问题发生的概率

Shenandoah收集器大致工作流程可以分为9个阶段:

步骤名称 动作
初始标记(Initial Marking) 标记与GC Roots直接关联的对象,这个阶段是Stop The World的,停顿时长与堆大小无关,与GC Roots的数量相关。
并发标记(Concurrent Marking) 遍历对象图,标记出全部可达的对象,这个阶段与用户线程一起并发的,时间长短取决于堆中存活对象的数量以及对象图的结构复杂程度。
最终标记(Final Marking) 处理剩余的SATB扫描,在这个阶段统计出回收价值最高的Region,将这些Region构成一组回收集,最终标记阶段也会有一小段短暂的停顿。
并发清理(Concurrent Cleanup) 清理那些整个区域内连一个存活对象都没有找到的Region(这类Region被称为Immediate Garbage Region)。
并发回收(Concurrent Evacuation) 核心差异!Shenandoah要把回收集里面的存活对象先复制一份到其他未被使用的Region之中。Shenandoah会通过读屏障和被成称为"Brooks Points"的转发指针来解决在复制对象时遇到的困难。时间长短取决于回收集的大小。
初始引用更新(Initial Update Reference) 把堆中所有指向旧对象的引用修正到复制后的新地址,这个操作称为引用更新。在此阶段,只是建立了一个线程集合点,确保所有的并发回收阶段中进行的收集器线程都已完成分配给它们的对象移动任务而已。时间会很短,有一个十分短暂的停顿。
并发引用更新(Comcurrent Update Reference) 真正开始进行引用更新操作,与用户线程一起并发的,时间长短取决于内存中涉及的引用数量的多少。只需要按照内存物理地址的顺序,线性搜索出引用类型,把旧值改为新值即可。
最终引用更新(Final Update Reference) 解决了堆中的引用更新后,还要修正存在于GC Roots中的引用。这个阶段是最后一次停顿,时间与GC Roots的数量有关。
并发清理(Concurrent Cleanup) 此时整个回收集中所有的Region已再无存活对象,都变成了Immediate Garbage Regions了,最后调用一次并发清理过程来回收这些Region的内存空间,供以后新对象分配使用。

Brooks PointsBrooks是一个人的名字,它提出使用了转发指针(Forwarding Pointer)的技术来实现对象移动与用户程序并发的一种解决方案。不需要用到内存保护陷阱,而是在原有对象布局结构的最前面统一增加一个新的引用字段,在正常不处于并发移动的情况下,该引用指向对象自己。实际上Shenandoah收集器是通过比较并交换(Compare And Swap, CAS)操作来保证并发时对象的访问正确性的。

JDK13中Shenandoah的内存屏障模型改进为基于引用访问屏障(Load Reference Barrier)的实现,所谓“引用访问屏障”是指内存屏障只拦截对象中数据类型为引用类型的读写操作,而不去管原生数据类型等其他非引用字段的读写。这能省去大量对原生类型、对象比较、对象加锁等场景中设置内存屏障所带来的消耗。

3.6.2 ZGC收集器

ZGC收集器是一款基于Region内存布局的,暂时不设分代的,使用了读屏障、染色指针和内存多重映射等技术来实现可并发的标记-整理算法的,以低延迟为首要目标的一款垃圾收集器。

ZGC的Region具有动态性-动态创建和销毁,以及动态的区域容量大小。

染色指针(Colored Pointer):一种直接将少量额外的信息存储在指针上的技术。尽管在linux下64位指针的高18位不能用来寻址,但是剩余的46位所能支持的64TB内存仍然能够充分满足需要。鉴于此,将其高4位提取出来存储四个标记信息。通过这些标志位,虚拟机可以直接从指针中看到其引用对象的三色标记状态、是否进入了重分配集、是否只能通过finalize( )方法才能被访问到。也使得ZGC能够管理的内存不可以超过4TB。使用染色指针的三大优势:

  1. 可以使得一旦某个Region的存活对象被移走之后,这个Region立即就能被释放和重用掉,不必等待整个堆中所有指向该Region的引用都被修正后才能清理。
  2. 可以大幅度减少在垃圾收集过程中的内存屏障的使用数量。到目前为止,ZGC都未使用写屏障,只使用了读屏障。
  3. 可以作为一种可扩展的存储结构用来记录更多与对象标记、重定位过程相关的数据,以便日后进一步提高性能。

Linux/x86-64平台上的ZGC使用了多重映射将多个不同的虚拟内存地址映射到同一个物理内存地址上,意味着ZGC在虚拟内存中看到的地址空间要比实际的堆内存容量来得更大。把染色指针中的标志位看作是地址的分段符,只要将这些不同的地址段都映射到同一个物理内存空间,经过多重映射转换后,就可以使用染色指针正常进行寻址了。

ZGC的运作过程(省略部分与之前介绍的G1和Shenandoah相同的小阶段部分):

步骤 动作
并发标记(Concurrent Mark) 遍历对象图做可达性分析的阶段,前后也要经过初始标记、最终标记的短暂停顿。ZGC的标记是在指针上而不是在对象上进行的,标记阶段会更新染色指针中的Marked 0、Marked 1 标志位。
并发预备重分配(Concurrent Prepare for Relocate) 根据特定的查询条件统计得出本次收集过程要清理那些Region,将这些Region组成重分配集。与G1收集器的回收集还是有区别的,ZGC的重分配集只是决定了里面的存活对象会被重新分配复制到其他的Region中,里面的Region会被释放,而并不能说回收行为就只是针对这个集合里面的Region进行,因为标记过程是针对全堆的。
并发重分配(Concurrent Reolcate) 核心阶段!把重分配集中的存活对象复制到新的Region上,并未重分配集中的每个Region维护一个转发表,记录从旧对象到新对象的转向关系。
并发重映射(Concurrent Remap) 修正整个堆中指向重分配集中旧对象的所有引用。

END

JVM学习笔记-第三章-垃圾收集器与内存分配策略的更多相关文章

  1. 《深入理解java虚拟机》第三章 垃圾收集器与内存分配策略

    第三章 垃圾收集器与内存分配策略 3.1 概述 哪些内存需要回收 何时回收 如何回收 程序计数器.虚拟机栈.本地方法栈3个区域随线程而生灭. java堆和方法区的内存需要回收.   3.2 对象已死吗 ...

  2. 读书笔记,《深入理解java虚拟机》,第三章 垃圾收集器与内存分配策略

    要实现虚拟机,其实人们主要考虑完成三件事情: 第一,哪些内存需要回收: 第二,什么时候回收: 第三,如何回收. 第二节,对象已死吗    垃圾收集其实主要是针对java堆里面的数据来说的,传统的垃圾收 ...

  3. 深入理解Java虚拟机学习笔记(二)-----垃圾收集器与内存分配策略

    写在前面 本节常见面试题: 如何判断对象是否死亡(两种方法). 简单的介绍一下强引用.软引用.弱引用.虚引用(虚引用与软引用和弱引用的区别.使用软引用能带来的好处). 如何判断一个常量是废弃常量 如何 ...

  4. <<深入Java虚拟机>>-第三章-垃圾收集器与内存分配策略-学习笔记

    垃圾收集 垃圾收集(Garbage Collection,GC),垃圾收集需要完成的三件事情. 哪些对象需要回收 什么时候回收 如何回收 如何确定对象已死(即不可能在被任何途径引用的对象) 引用计数算 ...

  5. 《深入理解JAVA虚拟机》----------第三章 垃圾收集器与内存分配策略,笔记(下)

    1.垃圾收集器 1.1 Serial收集器 这个收集器是一个单线程的收集器,它在进行垃圾收集时,必须暂停其他所有的工作线程. 它是虚拟机运行在Client模式下的默认新生代收集器,它简单而高效. 1. ...

  6. [Note][深入理解Java虚拟机] 第三章 垃圾收集器与内存分配策略笔记

    书上关于GCTimeRatio的讲解有点难以理解,查看Oracle的文档后重新理解了下 -XX:GCTimeRatio 运行时间 / GC时间 当GCTimeRatio为19时,运行时间是GC时间的1 ...

  7. 《深入理解Java虚拟机》(三)垃圾收集器与内存分配策略

    垃圾收集器与内存分配策略 详解 3.1 概述 本文参考的是周志明的 <深入理解Java虚拟机>第三章 ,为了整理思路,简单记录一下,方便后期查阅. 3.2 对象已死吗 在垃圾收集器进行回收 ...

  8. 《深入理解Java虚拟机》-----第3章 垃圾收集器与内存分配策略

    Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的“高墙”,墙外面的人想进去,墙里面的人却想出来. 3.1 概述 说起垃圾收集(Garbage Collection,GC),大部分人都把这 ...

  9. JVM学习笔记三:垃圾收集器与内存分配策略

    内存回收与分配重点关注的是堆内存和方法区内存(程序计数器占用小,虚拟机栈和本地方法栈随线程有相同的生命周期). 一.判断对象是否存活? 1. 引用计数算法 优势:实现简单,效率高. 致命缺陷:无法解决 ...

随机推荐

  1. 界面设计ie8 笔记

    1.ie8 jsp中设置 margin:0 auto 无效 解决方法  在html 标签改为<!DOCTYPE html> 2.ie8 input file 无法通过js触发点击事件,网上 ...

  2. Python小白的数学建模课-B5. 新冠疫情 SEIR模型

    传染病的数学模型是数学建模中的典型问题,常见的传染病模型有 SI.SIR.SIRS.SEIR 模型. 考虑存在易感者.暴露者.患病者和康复者四类人群,适用于具有潜伏期.治愈后获得终身免疫的传染病. 本 ...

  3. Java中有趣的移位操作!彻底弄懂各个移位操作符的使用方式

    << <<: 左移运算,左移几位就补几个0 >> >>: 右移运算,为算术右移 如果数字为正数时,移位后在前面补0 如果数字为负数时,移位后在前面补1 ...

  4. php混淆加密解密实战

    在查看别人的php源码的时候,我们经常会看到加密后的php代码.那么php加密原理是什么呢?怎么解密呢? 混淆加密 我们从百度随便搜索一个加密网站,例如:http://dezend.qiling.or ...

  5. IDA,IDA PRO 产品介绍

    IDA理念这是我们在开发产品时竭尽全力遵循的理念--在此过程中,我们相信我们将开发出能够为您带来所需的可靠性.便利性和易用性的软件.没有什么能打败人脑因为我们知道一秒钟的洞察力仍然胜过百年的处理时间, ...

  6. Elasticsearch-02-入门:集群、节点、分片、索引及常用API

    2. 基础入门 2.1 重要概念 2.1.1 集群和节点 1)cluster Elasticsearch集群是由一个或多个节点组成,通过其集群名称来进行唯一标识.节点在搜索到集群之后,通过判断自身的 ...

  7. Android单元测试问题解决

    1.'java.lang.RuntimeException: Method isEmpty in android.text.TextUtils not mocked'报错 https://www.ji ...

  8. Docker原理:Namespace

    目录 Namespace UTS Namespae PID Namespace Mount Namespace User Namespace Network Namespace 参考 Namespac ...

  9. Django基础012--接口开发

    全局参数(get,post,put,delete) 接口:/api/parameter GET:获取全局参数的所有数据 POST:创建全局参数 PUT:更新全局参数 DELETE:删除全局参数 1.创 ...

  10. 动态路由协议、RIP

    动态路由协议.RIP      一.动态路由协议        1)动态路由协议概述        2)度量值        3)收敛        4)静态路由与动态路由的比较        5)动 ...