锐单电子商城 , 一站式电子元器件采购平台!
  • 电话:400-990-0325

“最粉嫩”的JVM垃圾回收器及算法,抗极限面试

时间:2023-04-24 19:07:00 bsq073lvdt位移变送器bsq015c振动变送器

将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要按顺序分配内存即可,实现简单,运行高效。

“最粉嫩”的JVM垃圾回收器及算法,抗极限面试,倒背如流

但该算法的成本是将内存缩小到原来的一半。但需要注意的是,内存移动必须是真实的移动(复制),因此需要调整相应的参考(直接指针)。

复制回收算法适用于新一代,因为大多数对象日夜生死,所以复制过去的对象较少,效率自然较高,另一半的一次性清理速度非常快。

Appel式回收

一种更优化的复制回收分代策略:具体方法是分配更大的 Eden 区和两块较小 Survivor 空间(一般称为做From区和To区,也可以叫做S0和S1)

根据经验统计,新生代98%的对象是朝生夕死,不需要遵循 1:1 内存空间的比例被划分,但内存被分成更大的部分 Eden 空间和两块较小 Survivor 每次使用空间 Eden和其中一块Survivor[1]。回收时,将 Eden 和 Survivor 还活着的对象一次性复制到另一块 Survivor 空间上, 最后清理掉 Eden 和刚才用过的 Survivor 空间。

HotSpot 虚拟机默认 Eden 和 Survivor 大小比例是 8:1,即每一代新生代的内存空间都是整个新生代的容量 90%(80% 10%),只有10%的内存会被记录 “浪费”。当然,98%的对象可以回收,只是一般情况下的数据。我们不能保证每次回收只有不到10%的对象存活。 Survivor 当空间不足时,需要依靠其他内存(这里指老年人)来分配担保(Handle Promotion)

标记清除

算法分为两个阶段:标记和清除:首先扫描所有对象,标记所有标记的对象,所以需要扫描两次。回收效率略低,如果大多数对象日夜死亡,那么回收效率就会降低,因为需要大量的标记对象和回收对象,而复制效率较低。

其主要问题将在清除标记后产生大量不连续的内存碎片,太多的空间碎片可能会导致未来在程序运行过程中需要分配大对象时,无法找到足够的连续内存,不得不提前触发另一个垃圾回收动作。回收时,需要回收的对象越多,需要做的标记和清除的工作就越多,所以标记清除算法适用于老年代

标记整理

首先,标记所有需要回收的对象。标记完成后,后续步骤不是直接清理可回收对象,而是将所有存活对象移动到一端,然后直接清理端边界以外的内存。虽然标记分类算法没有内存碎片,但效率较低。

我们可以看到,标记分类和标记清除算法的区别主要在于对象的移动。对象移动不仅会增加系统负担,还需要暂停用户线程,所有参考对象都需要更新(直接指针需要调整)。因此,老年人使用的标记分类算法和标记分类算法各有优缺点。

垃圾回收器

回收器名称回收对象和算法回收器类型Serial新生代,复制算法线程(串行)Parallel Scavenge复制算法并行的新一代多线程回收器ParNew复制算法并行的新一代多线程回收器Serial Old在老年,标记算法单线程(串行)Parallel Old老年代,标记整理算法并行的多线程回收器CMS在老年人,标记清除算法并发的多线程回收器G新一代,老一代;标记整理; 多线程回收器化为零并发

不用说,目前最常用的两种垃圾回收器一定是CMS和G一般面试官会问CMS和G1的差异和各自的特点,不会深入问实现原则,毕竟Java面试问的知识点太多了,一个个深入问一个小时的面试时间是不够的。

串行垃圾回收器就不说了,这里就来说说并发垃圾回收器

CMS(Concurrent Mark Sweep)回收器

顾名思义,这是并发的垃圾回收器,这种回收器是一种以获取最短的回收停顿时间为目的的垃圾收集器,目前很大一部分Java互联网应用或B/S在系统服务器上,由于这类应用特别注重相应的速度,希望系统停顿时间越短越好,用户体验也会越好,CMS非常符合这类应用的需求。

从名称可以看出,该回收器是基于标记清除算法实现的,其运行过程相对复杂,分为以下四个步骤

初始标记:很短,只是标记下来GC Root能直接关联的对象,速度极快。

并发标记:与用户应用同时进行,GC Root跟踪过程,标记GC Root所有开始关联的对象,开始遍历整个可达分析的路径对象,时间长,并发。

重新标记:短时间内,为了纠正并发标记期间因用户程序继续运行而导致标记变化的部分对象的标记记录,本阶段的停顿时间通常比初始标记更长 记录阶段稍长,但远短于并发标记。

并发清除:收集器线程可以与用户线程一起工作,因为整个过程中耗时最长的并发标记和并发清除过程。因此,一般来说,CMS 与用户线程一起执行内存回收过程。-XX: UseConcMarkSweepGC ,表示新一代使用ParNew,老年代的用 CMS。

CPU 敏感:CMS 对处理器资源敏感,毕竟采用并发收集,核心处理不足 4 个时,CMS 对用户的影响较大。

浮动垃圾:由于 CMS 并发清理阶段,用户线程仍在运行,随着程序运行,自然会有新的垃圾不断产生,这部分垃圾出现在标记过程中,CMS他们不能在当次收集中处理,所以他们必须留下来GC再次清理。这部分垃圾被称为浮动垃圾。由于浮动垃圾的存在,需要预留部分内存,这意味着 CMS 收集不能像其他收集器一样等待老年人的到来。在1.如果预留的内存不足以存放浮动垃圾,6版中老年空间利用率阈值(92%)就会出现 Concurrent Mode Failure,此时,虚拟机将临时启用 Serial Old 来替代 CMS。

会产生空间碎片:标记 - 一般来说,清除算法会导致不连续的空间碎片,CMS是JVM 推出了第一款并发垃圾收集器,所以还是很有代表性的。但最大的问题是 CMS 采用标记清除算法,因此会有内存碎片,当碎片较多时,会给大对象的分配带来很大的麻烦,为了解决这个问题,CMS 提供一个 参数:-XX: UseCMSCompactAtFullCollection,一般来说,它是打开的。如果不能分配大对象,则应整理内存碎片。这个地方通常使用 Serial Old ,因为 Serial Old 是单线程,所以如果内存空间大,对象多,CMS 这种情况会很卡。

总结:CMS 问题很多,所以JDK默认垃圾回收器没有版本CMS,只能手动指定。但它毕竟是第一个并发垃圾回收器,对于解并发垃圾回收有一定的意义,所以一定要理解。为什么 CMS 使用标记-清除。在实现并发垃圾回收时,如果采用标记分类算法,则还涉及对象的移动(对象的移动必须涉及引用的变化,需要暂停业务线程来处理堆栈信息,使并发收集的暂停时间更长),因此可以减少使用简单的标记-清除算法 CMS的STW的时间。

垃圾回收器适用于回收堆空间 G至20G。

G1(Garbage First)

随着JVM增加内存,STW的时间成为JVM 急需解决的问题,但如果按照传统的分代模型,总是跳不出来STW时间不可预测。

为了实现STW时间可以预测,首先要有思想上的改变。

G1将堆内存化为零(Region),每一个Region 可根据需要扮演新一代Eden空间、Survivor空间,或老年空间。

回收器可以扮演不同的角色 Region 采用不同的策略来处理,这样新创建的对象和已经存活了一段时间并经历过多次收集的旧对象都可以获得良好的收集效果。

Region:Region可能是Eden,也有可能是Survivor,也有可能是Old,另外 Region 还有一种特殊的Humongous专门用于存储大对象的区域。G认为只要大小超过一个,Region一半容量的对象可以判断为大对象。每个Region参数可以通过大小-XX:G1HeapRegionSize 设定,取值范围为 1MB至32MB,而且应该是2N次幂。对于那些超越整个过程的人来说, Region 容量的超大对象将储存在 N 个连续的 Humongous Region 之中,G1 在大多数情况下,它被回收 Humongous Region 作为老年人的一部分。

开启参数-XX: UseG1GC分区大小-XX: G1HeapRegionSize一般建议随着值的增加而逐渐增加 size 垃圾的存活时间增加,GC 隔更长,但每次 GC 的时间也会更长。

最大GC暂停时间 -XX:MaxGCPauseMillis设置最大GC暂停时间的目标(单位毫秒),这是个软目标,JVM会尽最大可能实现它。

运行过程如下:

初始标记:仅仅只是标记一下GC Roots能直接关联到的对象,并且修改 TAMS 指针的值,让下一阶段用户线程并发运行时,能正确地在可用的 Region 中分配新对象。这个阶段需要停顿线程,但耗时很短,而且是借用进行Minor GC的时候同步完成的,所以G1收集器在这个阶段实际并没有额外的停顿。要达到GC与用户线程并发运行,必须要解决回收过程中新对象的分配,所以G1为每一个Region 区域设计了两个名为TAMS(Top at Mark Start)的指针,从 Region 区域划出一部分空间用于记录并发回收过程中的新对象。这样的对象认为它们是存活的,不纳入垃圾回收范围。

并发标记:从GC Root开始对堆中对象进行可达性分析,递归扫描整个堆里的对象图,找出要回收的对象,这阶段耗时较长,但可与用户程序并发执行。当对象图扫描完成以后,并发时有引用变动的对象,这些对象会漏标,漏标的对象会被一个叫做SATB(snapshot at the beginning)算法来解决。

最终标记:对用户线程做另一个短暂的暂停,用于处理并发阶段结后仍遗留下来的最后那少量的 SATB 记录(漏标对象)。

筛选回收:负责更新Region的统计数据,对各个Region的回收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划,可以自由选择任意多个Region构成回收集,然后把决定回收的那一部分 Region 的存活对象复制到空的Region中,再清理掉整个旧 Region 的全部空间。这里的操作涉及存活对象的移动,是必须暂停用户线程,由多条收集器线程并行完成的。

总结:并行与并发:G1 能充分利用多 CPU、多核环境下的硬件优势,使用多个 CPU(CPU 或者 CPU 核心)来缩短 Stop-The-World 停顿的时间,部分其他收集器 原本需要停顿 Java 线程执行的 GC 动作,G1 收集器仍然可以通过并发的方式让 Java 程序继续执行。

分代收集:与其他收集器一样,分代概念在 G1 中依然得以保留。虽然 G1 可以不需要其他收集器配合就能独立管理整个 GC 堆,但它能够采用不同的方式 去处理新创建的对象和已经存活了一段时间、熬过多次 GC 的旧对象以获取更好的收集效果。

空间整合:与 CMS 的“标记—清理”算法不同,G1 从整体来看是基于“标记—整理”算法实现的收集器,从局部(两个 Region 之间)上来看是基于“复 制”算法实现的,但无论如何,这两种算法都意味着 G1 运作期间不会产生内存空间碎片,收集后能提供规整的可用内存。这种特性有利于程序长时间运 行,分配大对象时不会因为无法找到连续内存空间而提前触发下一次 GC。

追求停顿时间:-XX:MaxGCPauseMillis 指定目标的最大停顿时间,G1 尝试调整新生代和老年代的比例,堆大小,晋升年龄来达到这个目标时间。

并发标记

三色标记算法

说到并发标记,就不能不提下并发标记中的三色标记算法,它是一种描述追踪式回收器的有效的办法,利用它可以推演回收器的正确性。

在三色标记法之前有一个算法叫 Mark-And-Sweep(标记清除)。这个算法会设置一个标志位来记录对象是否被使用。最开始所有的标记位都是0,如果发现对象是可达的就会置为1,一步步下去就会呈现一个类似树状的结果。等标记的步骤完成后,会将未被标记的对象统一清理,再次把所有的标记位 设置成0方便下次清理。

这个算法最大的问题是 GC 执行期间需要把整个程序完全暂停,不能异步进行 GC 操作。因为在不同阶段标记清扫法的标志位0和1有不同的含义,那么新增的对象无论标记为什么都有可能意外删除这个对象。对实时性要求高的系统来说,这种需要长时间挂起的标记清扫法是不可接受的。所以就需要一个算法来解决 GC 运行时程序长时间挂起的问题,那就三色标记法。三色标记最大的好处是可以异步执行,从而可以以中断时间极少的代价或者完全没有中断来进行整个GC。

我们将对象分为三种类型:

黑色:根对象,或者该对象与它的子对象都被扫描过。

灰色:对本身被扫描,但是还没扫描完该对象的子对象。

白色:未被扫描对象,如果扫描完所有对象之后,最终为白色的为不可达对象,既垃圾对象。

以上图为例,简单说下三色标记的实现原理,首先如上图所示,线程1已完成所有标记,所有对象都被标记成黑色;线程2还处于半完成状态,其中对象B本身已被扫描,但是还没有扫描该对象的子对象。

…(img-CBDQ0bSq-1640192014119)]

以上图为例,简单说下三色标记的实现原理,首先如上图所示,线程1已完成所有标记,所有对象都被标记成黑色;线程2还处于半完成状态,其中对象B本身已被扫描,但是还没有扫描该对象的子对象。

锐单商城拥有海量元器件数据手册IC替代型号,打造电子元器件IC百科大全!

相关文章