JVM虚拟机(二)如何定位垃圾、判断对象是否死亡?垃圾回收算法、垃圾回收器、CMS、G1垃圾回收器

news2024/9/20 8:08:33

一、GC基本信息

1.1 什么是GC,垃圾回收?

JVM的垃圾回收(Garbage Collection,GC)是一种自动内存管理机制,其主要目的是识别并清除不再使用的对象,释放内存空间以供应用程序中的其他部分使用。GC的要点和机制主要包括以下几个方面:

1. 分代收集算法:JVM通常将堆内存分为新生代和老年代,新生代存放新创建的对象,老年代存放长时间存活的对象。新生代的GC(Minor GC)频繁且速度快,老年代的GC(Major GC 或 Full GC)频率低但耗时长。

2. 垃圾回收算法:常见的垃圾回收算法包括标记-清除、复制、标记-整理等。标记-清除算法分为标记和清除两个阶段,但可能产生内存碎片;复制算法通过复制存活对象到另一块内存来减少碎片,但需要额外内存空间;标记-整理算法通过移动对象来减少碎片,但需要更多计算。

3. 垃圾回收器类型:JVM提供了多种垃圾回收器,如Serial、Parallel、CMS、G1等,每种回收器适用于不同的应用场景和性能要求。

4. 性能调优:通过监控和分析GC日志,可以对JVM进行性能调优。例如,可以通过调整堆大小、选择合适的垃圾回收器、优化代码等方式来提高应用程序的性能。

5. 内存分配策略:对象通常首先在新生代的Eden区分配,经过GC后存活的对象会进入Survivor区,并随着年龄增长可能被晋升到老年代。

6. 可达性分析:JVM使用可达性分析来确定对象是否存活,以GC Roots为起点,通过引用链判断对象是否可达。

7. GC日志监控与分析:使用工具如jstat、VisualVM等监控GC事件和内存使用情况,分析GC日志以优化GC配置。

通过深入理解JVM的垃圾回收机制,开发者可以更有效地优化Java应用程序的性能,处理内存相关问题,并提高系统的稳定性和响应速度。

1.2 如何定位垃圾、判断对象是否死亡?

堆中几乎放着所有的对象实例,对堆垃圾回收前的第⼀步就是要判断哪些对象已经死亡(即不能再被任何途径使用的对象)。

(1)引用计数法

给对象中添加⼀个引⽤计数器,有对象引用时,计数器就加1;当引用失效,计数器就减1;计数器为0就可以被回收。

(缺点:引用计数法无法解决对象之间循环引用的问题:A对象引用B,B对象引用A

(2)可达性分析算法

通过一些列的称为GC Roots 的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots之间没有任何引用链相连,则证明这个对象是不可用的。

(3)GC Roots包括哪些?

1、虚拟机栈中引用的对象,譬如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等。

2、方法区中静态变量、常量引用的对象。譬如Java类的引用类型 静态变量。

3、 【本地Native方法】中引用的对象

4、 所有被同步锁(synchronized关键字)持有的对象

5、 Java虚拟机内部的引用,如基本数据类型对应的Class对象,

6、 字符串常量池里的引用。

7、一些常驻的异常对象(比如NullPointExcepiton、OutOfMemoryError)等,还有系统类加载器。

判断不可达一定会被回收吗 ?

即使在可达性分析算法判断不可达的对象,也并非是“非死不可的”。

如果对象在进行可达性分析后发现没有与GC Roots相连的引用链,那它将会被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。当对象没有覆盖finalize()方法或者finalize()方法已经被调用过,finalize()方法都不会执行,该对象将会被回收。

如果这个对象被判定为有必要执行finalize()方法,那么这个对象将会被放置在一个叫做F-Queue的队列中,然后由Finalizer线程去执行。GC将会对F-Queue中的对象进行第二次标记,如果对象在finalize()方法中重新与引用链上的任何一个对象建立关联,那么在第二次标记时将会被移除“即将回收”的集合,否则该对象将会被回收。

(比如:把自己(this关键字)赋值给某个类变量(static修饰)或者对象的成员变量)

二、常见的垃圾回收算法有哪些?(标记清除、标记整理、复制算法)

(1) 标记清除

(Mark-Sweep):该算法会从每个GC Roots出发,依次标记有引用关系的对象,最后将没有被标记的对象清除。

缺点:效率不高,会产生碎片

为了解决这个问题,又提出了“标记-整理算法”,该算法类似计算机的磁盘整理,首先会从GC Roots出发标记存活的对象,然后将存活对象整理到内存空间的一端,形成连续的已使用空间,最后把已使用空间之外的部分全部清理掉,这样就不会产生空间碎片的问题

(2) 标记复制---用于survivor区域

(Mark-Copy):将空间分为大小相等的两块,只将数据存储在其中一块上

  • 当需要回收时,首先标记垃圾对象

  • 然后将存活对象复制到另一块内存

  • 最后将第一块内存空间全部清除!

分析

  • 避免了空间碎片,但内存缩小了一半。

  • 效率不高:每次都需将有用数据全部复制到另一片内存

(3) 标记整理算法--用于老年代

(Mark-Compact):在回收前,标记过程仍与"标记-清除"一样 但后续不是直接清理可回收对象,而是

  • 将所有存活对象移到一端

  • 直接清掉端边界之外内存

分析:

这是一种老年代垃圾收集算法. 老年代中对象一般寿命较长,每次垃圾回收会有大量对象存活 因此如果选用"复制"算法,每次需要較多的复制操作,效率低

而且,在新生代中使用"复制"算法 当 Eden+Survior 都装不下某个对象时,可使用老年代内存进行"分配担保"

而如果在老年代使用该算法,那么在老年代中如果出现 Eden+Survior 装不下某个对象时,没有其他区域给他作分配担保

因此,老年代中一般使用标记整理算法

三种算法的缺点:

  1. 标记清除:先标记,标记完毕之后再清除,效率不高,会产生大量的空间碎片。导致需要分配一个较大连续空间时容易触发FullGC。

  2. 标记复制:有一半的空间是浪费的,分为 8:1 的 Eden 区和 survivor 区,就是上面谈到的 YGC

  3. 标记整理:效率不高,标记完毕之后,让所有存活的对象向一端移动

三、 GC分哪三种,分别采用什么算法?

堆内存空间分为较大的Eden和两块较小的Survivor,每次只使用Eden和Survivor区的一块。在新生代中,由于大量对象都是"朝生夕死",也就是一次垃圾收集后只有少量对象存活 因此我们可以将内存划分成三块。

  • Eden、Survior0、Survior1

  • 内存大小分别是8:1:1

分配内存时,只使用Eden和一块Survior0,当发现Eden+Survior0的内存即将满时,JVM会发起一次Minor GC,清除掉废弃的对象,并将所有存活下来的对象复制到Survior1中。

通过这种方式,只需要浪费10%的内存空间即可实现带有压缩功能的垃圾收集方法,避免了内存碎片的问题.

分配担保

准备为一个对象分配内存时,发现此时Eden+Survior中空闲的区域无法装下该对象 就会触发MinorGC(新生代 GC 算法),对该区域的废弃对象进行回收。

但如果MinorGC过后只有少量对象被回收,仍然无法装下新对象

  • 那么此时需要将Eden+Survior中的所有对象转移到老年代中,然后再将新对象存入Eden区.这个过程就是"分配担保".

在发生 minor gc 前,虚拟机会检测老年代最大可用连续空间是否大于新生代所有对象总空间 若成立,minor gc 可确保安全 若不成立,JVM会查看 HandlePromotionFailure是否允许担保失败

  1. 伊甸园Eden,最初对象都分配到这里,与幸存区合称新生代

  2. 幸存区 Survivor,当伊甸园内存不足,回收后的幸存对象到这里,分成 from 和 to,采用标记复制算法

  3. 老年代 old,当幸存区对象熬过15次回收,晋升到老年代【幸存区内存不足或大对象会导致提前晋升】

(1)Minor GC:

发生在新生代的垃圾回收,暂停时间。短清理整个YouGen的过程,eden的清理

(2)Mixed GC:

新生代 + 老年代部分区域的垃圾回收,G1 收集器特有

(3)Full GC:

新生代 + 老年代完整垃圾回收,暂停时间长,应尽力避免。Full GC 是清理整个堆空间—包括年轻代和永久代

Minor GC 和Full GC区别?

新生代内存不够用时候发生 MGC 也叫 YGC,JVM 内存不够的时候发生 FGC

  • Minor GC: 发生在新生代的垃圾回收

  • Full GC: 新生代 + 老年代完整垃圾回收,暂停时间长,应尽力避免。

Full GC 触发的场景?

  • 显示调用了System.gc()。

  • 堆内存分配过小

  • 大对象

  • 内存泄漏

  • 老年代空间不足。Minor GC时空间分配担保失败,新生代对象晋级老年代,大对象分配空间等情况。如果Full GC后空间仍然不足,则抛出错误:java.lang.OutOfMemoryError: Java heap space。

  • 永久代或者元空间满了。如果经过Full GC仍然回收不了,那么JVM会抛出错误信息:java.lang.OutOfMemoryError: PermGen space 。

  • 大量的线程回收,导致GC时间很长。

频繁FullGC如何排查?

首先判断FullGC后能不能有效的把内存回收掉,同时把堆dump下来,分析FullGC的原因:

如果FullGC没有效果,产生了内存泄漏,查看代码。

如果FullGC有效果,有可能是堆内存分配不合理。

  • 如果是一次fullgc后,剩余对象不多。那么说明你eden区设置太小,导致短生命周期的对象进入了old区。

  • 如果一次fullgc后,old区回收率不大,那么说明old区太小。

四、垃圾回收器有哪些?

垃圾回收器主要分为两种模式:一个是分代模型,一个是分区模型

垃圾收集器主要有:Serial、Serial Old、ParNew、Parallel Scavenge、Parallel Old、CMS、G1

(1)Serial、Serial Old(几兆-几十兆)

Serial收集器:一个新生代、单线程的收集器,采用标记-复制算法。它在进行垃圾回收时,必须暂停所有用户线程,直到它收集结束。(stop the world)

Serial Old收集器:一个老年代、单线程的收集器,采用标记-整理算法。它在进行垃圾回收时,必须暂停所有用户线程,直到它收集结束。(stop the world)

(2)Parallel【JDK1.8默认--多线程】

Parallel Scavenge、Parallel Old

JDK 1.8 默认采用的是ParallelGC ,老年代默认就是Parallel old 了

PS+PO、并行(几十兆-几百兆)

大部分系统使用PS+PO(分代模型 )

  • ①eden 内存不足发生 Minor GC,标记复制 STW

  • ②old 内存不足发生 Full GC,标记整理 STW

  • 注重吞吐量

在注重吞吐量或者处理器资源较为稀缺的场合,都可以优先考虑PS+PO收集器这个组合。

Parallel Scavenge收集器是一款新生代收集器,基于标记-复制算法实现的。能够进行并行收集的多线程收集器。

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

(3)CMS+ParNew

ParNew收集器:一个新生代、多线程的收集器,采用标记-复制算法。只有Serial和ParNew收集器能与CMS配合工作。ParNew收集器是激活CMS后的默认新生代收集器。

CMS收集器(ConcurrentMarkSweep)是老年代收集器,基于标记-清除算法实现的,适合8G-16G左右的机器;【标记清理算法 】

  • 一个业务线程,一个垃圾回收线程。
  • old 并发标记,重新标记时需要 STW,并发清除

  • Failback Full GC

  • 注重响应时间

CMS垃圾回收过程?

 

CMS收集器是一种老年代区域的垃圾收集器,往往配合ParNew收集器来使用。它适合在注重用户体验的应用上使用,实现了GC线程和用户线程并发进行。 采用了标记清除算法。回收过程大致分为5个步骤:

  1. 初始标记(STW):暂停其他工作线程(STW),标记GC roots 直接引用的对象。过程很快

  2. 并发标记:从GC roots 直接引用的对象出发向下查找所有引用的对象。这个过程最耗时,和用户线程并发执行。【这个过程可能出现的问题:用户线程执行中可能产生新的垃圾(浮动垃圾),无法被标记。】

  3. 重新标记(STW):为修正 并发标记 中产生变动的对象标识,主要用三色标记中的增量更新算法来进行标记。这个过程会暂停其他进程(STW)。

  4. 并发清除:将标记为可回收的对象进行回收,和用户线程并发执行。【这个过程也会产生新垃圾对象(浮动垃圾),这些对象将在下次GC时回收】

  5. 并发重置:将标记为不可回收的对象的标志清除。

三色标记算法

为什么CMS的GC线程可以和用户线程一起工作?----三色标记算法

1.用三种颜色记录对象的标记状态

  • ①黑色 – 已标记

  • ②灰色 – 标记中

  • ③白色 – 还未标记

- 刚开始,所有的对象都是白色,没有被访问。
- 将GC Roots直接关联的对象置为灰色。
- 遍历灰色对象的所有引用,遍历后灰色对象本身置为黑色,引用置为灰色。
- 重复步骤3,直到没有灰色对象为止。
- 结束时,黑色对象存活,白色对象回收。

CSM缺点?

  1. 并发回收导致CPU资源紧张: 在并发阶段,它虽然不会导致用户线程停顿,但却会因为占用了一部分线程而导致应用程序变慢,降低 程序总吞吐量。CMS默认启动的回收线程数是:(CPU核数 + 3)/ 4,当CPU核数不足四个时,CMS对 用户程序的影响就可能变得很大。

  2. 无法清理浮动垃圾: 在CMS的并发标记和并发清理阶段,用户线程还在继续运行,就还会伴随有新的垃圾对象不断产生,但 这一部分垃圾对象是出现在标记过程结束以后,CMS无法在当次收集中处理掉它们,只好留到下一次垃 圾收集时再清理掉。这一部分垃圾称为“浮动垃圾”。

  3. 并发失败(Concurrent Mode Failure): 由于在垃圾回收阶段用户线程还在并发运行,那就还需要预留足够的内存空间提供给用户线程使用,因 此CMS不能像其他回收器那样等到老年代几乎完全被填满了再进行回收,必须预留一部分空间供并发回 收时的程序运行使用。默认情况下,当老年代使用了 92% 的空间后就会触发 CMS 垃圾回收,这个值可 以通过 -XX: CMSInitiatingOccupancyFraction 参数来设置。 这里会有一个风险:要是CMS运行期间预留的内存无法满足程序分配新对象的需要,就会出现一次“并发 失败”(Concurrent Mode Failure),这时候虚拟机将不得不启动后备预案:Stop The World,临时启 用 Serial Old 来重新进行老年代的垃圾回收,这样一来停顿时间就很长了。

  4. 内存碎片问题: CMS是一款基于标记清除算法实现的回收器,这意味着回收结束时会有内存碎片产生。内存碎片过多时,将会给大对象分配带来麻烦,往往会出现老年代还有很多剩余空间,但就是无法找到足够大的连续 空间来分配当前对象,而不得不提前触发一次 Full GC 的情况。

为了解决这个问题,CMS收集器提供了一个 -XX:+UseCMSCompactAtFullCollection 开关参数(默认开 启),用于在 Full GC 时开启内存碎片的合并整理过程,由于这个内存整理必须移动存活对象,是无法并发的,这样停顿时间就会变长。还有另外一个参数 -XX:CMSFullGCsBeforeCompaction,这个参数的 作用是要求CMS在执行过若干次不整理空间的 Full GC 之后,下一次进入 Full GC 前会先进行碎片整理 (默认值为0,表示每次进入 Full GC 时都进行碎片整理)。

为什么CMS要用标记-清除,不用标记-整理算法?

【答】因为CMS作为第一款实现用户线程和收集线程并发执行的收集器!当时的设计理念是减少停顿时间,最好是能并发执行!但是问题来了,如要用户线程也在执行,那么就不能轻易的改变堆中对象的内存地址!不然会导致用户线程无法定位引用对象,从而无法正常运行!而标记整理算法和标记复制算法都会移动存活的对象,这就与上面的策略不符!因此CMS采用的是标记清理算法。

CMS的垃圾回收阶段是并发回收的,如果使用标记整理的话,对象的内存地址会进行移动,因为用户线程还在执行,为了避免因内存地址移动而带来的bug,还需要对用户线程的对象指针进行维护,在这个过程中肯定会STW,这样做就提高了垃圾清理的时长,停顿时间也变长了,不符合CMS以获取最短回收停顿时间为目标的设计初衷。

总结:

CMS的回收周期很长,但是他的STW时间是分开的,比如总的STW要100ms,可能他会在初始标记消耗20ms,重新标记消耗80ms,对于用户来说能感知的到停顿时长可能只有80ms,也就是说CMS的设计初衷是为了提高用户体验,减少停顿时间。这是和Parallel最大的不同。正因为CMS的回收周期很长,所以在垃圾很多的情况下可能出现上次的GC周期还没执行完就又触发了GC,被称为”concurrent mode failure“;对于这种情况会回退到Serial的方式进行回收,全程STW。因为是采用标记清除算法,所以会存在内存碎片的问题,通过参数-XX:+UseCMSCompactAtFullCollection 可以设置清除之后再做一次整理。

  • CMS相关参数 -XX:+UseConcMarkSweepGC:使用CMS垃圾收集器,当设置这个参数后,年轻代默认会开启ParNew。 -XX:ConcGCThreads:并发的GC线程数,默认是CPU的核数。 -XX:+UseCMSCompactAtFullCollection:相当于标记整理。 -XX:CMSFullGCsBeforeCompaction:多少次FullGC之后压缩一次,默认是0。 -XX:CMSInitiatingOccupancyFraction: 当老年代使用达到该比例时会触发FullGC,默认是92。 -XX:+UseCMSInitiatingOccupancyOnly:这个参数搭配上面那个用,表示是不是要一直使用上面的比例触发FullGC,如果设置则只会在第一次FullGC的时候使用-XX:CMSInitiatingOccupancyFraction的值,之后会进行自动调整。 -XX:+CMSScavengeBeforeRemark:在FullGC前启动一次MinorGC,目的在于减少老年代对年轻代的引用,降低CMS GC的标记阶段时的开销,一般CMS的GC耗时80%都在标记阶段。 -XX:+CMSParallellnitialMarkEnabled:默认情况下初始标记是单线程的,这个参数可以让他多线程执行,可以减少STW。 -XX:+CMSParallelRemarkEnabled:使用多线程进行重新标记,目的也是为了减少STW。

Serial GC、 Parallel GC 、CMS+PN这三个缺点:操作必须扫描整个老年代、不太适合大内存,年轻代老年代都是独立的内存块、大小必须要提前确定。

(4)G1-Garbage-First【JDK1.9默认】

G1 的全称是 Garbage-First【垃圾优先】G1 收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的 Region。G1 成为【JDK9的默认垃圾回收器】,取代了 PS+ PO 的组合,而 CMS 被声明为不推荐使用的垃圾回收器

Region-分区回收---(堆内存的分配)

G1 收集器采用了和此前完全不同的堆内存分配方式,他将堆内存分为2048个相同大小的region(单个大小为1MB~32MB),这些regions在逻辑上被动态的分为4种,每个区域都可以充当 eden,survivor,old, humongous(巨大)

  1. Eden

  2. Survivor

  3. old generation

  4. Humongous regions  (G1还新增一种新的类型)。如果对象超过所在regions的50%,就会被移入这个类型的region。这种大对象应尽量避免创建

G1用的哪种垃圾回收算法?

G1收集器采用“标记-复制”和“标记-整理”

  • 从整体看,是基于“标记整理”,

  • 从局部看,两个region之间是“标记复制”。

G1垃圾回收过程?

  1. 初始标记(会STW)

    该阶段标记所有从GC根对象可达的存活对象。 这个阶段需要停顿线程,与进行Minor GC的时候同步完成的,因此耗时很短。
  2. 并发标记:

    1. 在这个阶段,G1 GC会扫描整个堆,标记出所有存活的对象。这个过程是在程序继续运行的情况下并发执行的,因此不会引起长时间的暂停。

    2. G1 GC会跟踪哪些区域的回收收益最大,以便后续优先进行回收。

  3. 最终标记(会STW)

    • 这个阶段暂停应用线程,处理并发阶段结束后仍有引用变动的对象。

    • 该阶段通常暂停时间较短。

  4. 清理阶段(会STW)

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

    • 存活的对象会从旧的区域移动到新的区域中,回收未使用的区域。

    • 该过程通常是并行执行的,以减少暂停时间。

  6. 混合回收

    • 混合GC阶段既回收年轻代又回收一部分旧年代区域。它在年轻代GC的基础上,混合回收堆中那些收益较高的旧代区域。

    • 这个阶段的目的是在控制暂停时间的前提下,逐步减少旧代中的垃圾对象。

G1 回收阶段 - 并发标记与混合收集

  1. 当老年代占用内存超过阈值后,触发并发标记,这时无需暂停用户线程

  2. 并发标记之后,会有重新标记阶段解决漏标问题,此时需要暂停用户线程。这些都完成后就知道了老年代有哪些存活对象,随后进入混合收集阶段。此时不会对所有老年代区域进行回收,而是根据暂停时间目标优先回收价值高(存活对象少)的区域(这也是 Gabage First 名称的由来)。

  3. 混合收集阶段中,参与复制的有 eden、survivor、old,下图显示了伊甸园和幸存区的存活对象复制

  4. 下图显示了老年代和幸存区晋升的存活对象的复制

  5. 复制完成,内存得到释放。进入下一轮的新生代回收、并发标记、混合收集

G1 GC的优势和缺点

优点:

  • 可预测性:G1 GC的设计目的是控制和减少垃圾回收的长暂停。

  • 区域化管理:通过分区来管理内存,使得垃圾回收操作更加灵活,优先回收那些回收价值最高的区域。

  • 并发处理:G1 GC的多个阶段是并发执行的,可以有效减少暂停时间,适合大内存应用。

总结来说,G1 GC通过初始标记、并发标记、最终标记、清理、并行回收和混合回收阶段来优化垃圾回收流程,尤其适用于低停顿、可预测的垃圾回收需求的场景。

缺点:

以空间换时间,浪费了很多内存

G1的官方推荐

期望GC-STW时间

G1 新增一个配置(-XX:MaxGCPauseMillis=200),来设置我们期望每次GC-STW的时间。这是一个相对值,不会严格按照这个时间执行,jvm 会评估GC总的时间,如果不能满足这个期望时间,JVM 会通过一定的算法,只回收部分性价比高的内存空间,来达到这个目标。

  1. 不要设置年轻代大小

    • 设置年轻一代的大小会禁用期望GC-STW时间的目标。

    • G1不再能够根据需要扩展和缩小年轻一代的空间。由于尺寸是固定的,因此无法更改尺寸。

  2. 期望GC-STW时间

    • 最好不要设置平均GC时间,而是使用 90% 的GC时间.

  3. Mixed GC 中 Evacuation Failure

    • 当没有更多的空闲region被提升到老一代或者复制到幸存空间时,并且由于堆已经达到最大值,堆不能扩展,从而发生Evacuation Failure。此时会触发full GC ,类似与 Serial 收集器的单线程垃圾回收,非常耗时

    • 增加(-XX:G1ReservePercent=10)的大小

    • 降低(-XX:InitiatingHeapOccupancyPercent=45)的大小

    • 增加(-XX:ConcGCThreads=n)并发标记线程数

(5)ZGC

CMS和G1如何解决并发漏标问题?

漏标和错标存在两个充要条件:

  • 有至少一个黑色对象在自己被标记之后指向了这个白色对象

  • 所有的灰色对象在自己引用扫描完成之前删除了对白色对象的引用

这两个条件,必须全满足,才会造成漏标问题。换言之,我们破坏任何一个条件,这个白色对象。就不会再被漏标。

如此一来这样就产生了两个解决办法:

①CMS采用增量更新:Incremental Update(CMS)

  • 只要赋值发生,被赋值的对象就会被记录

  • 记录黑色新增的引用(底层用的写屏障)

增量更新破坏的是第一个条件,我们在这个黑色对象增加了对白色对象的引用之后,将它的这个引用,记录下来,在最后标记的时候,再以这个黑色对象为根,对它的引用进行重新扫描。

可以简单理解为,当一个黑色对象增加了对白色对象的引用,那么这个黑色对象就被变灰。

缺点:就是会重新扫描这个黑色对象的所有引用,比较浪费时间。

②G1采用原始快照:Snapshot At The Beginning,SATB(G1)

  • 新加对象会被记录

  • 被删除引用关系的对象也被记录

原始快照破坏的是第二个条件,我们在这个灰色对象取消对白色对象的引用之前,将这个引用记录下来,在最后标记的时候,再以这个引用指向的白色对象为根,对它的引用进行扫描。

可以简单理解为,当一个灰色对象取消了对白色对象的引用,那么这个白色对象被变灰。

这样做的缺点就是,这个白色对象有可能并没有黑色对象去引用它,但是它还是被变灰了,就会导致它和它的引用,本来应该被垃圾回收掉,但是此次GC存活了下来,就是所谓的浮动垃圾.

其实这样是比较可以忍受的,只是让它多存活了一次GC而已,浪费一点点空间,但是会比增量更新更省时间。

【面试题】:CMS和G1的异同?

G1 垃圾收集器相比与 CMS 收集器,G1 收集器两个最突出的改进是:

  1. 基于标记-整理算法,不产生内存碎片。

  2. 可以非常精确控制停顿时间,在不牺牲吞吐量前提下,实现低停顿垃圾回收。

G1 收集器避免全区域垃圾收集,它把堆内存划分为大小固定的几个独立区域,并且跟踪这些区域的垃圾收集进度,同时在后台维护一个优先级列表,每次根据所允许的收集时间,优先回收垃圾最多的区域。 区域划分和优先级区域回收机制,确保 G1 收集器可以在有限时间获得最高的垃圾收集效率。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2065442.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

go+gin+vue入门

后端框架 1、安装go、goland 2、创建空项目 3、下载要用的包:命令行输入go get -u github.com/xxxx 4、安装mysql数据库,使用navicat创建数据库。 5、按照项目框架搭建目录、文件、代码:如router、model… 6、运行测试,go run ma…

C语言基础(十一)

1、指针: C语言中的指针是一种非常重要的数据类型,可以直接访问和操作内存地址。指针存储变量的内存地址,而不是变量的值本身。通过使用指针,可以灵活地控制数据的存储和访问,实现复杂的数据结构如链表、树。 定义指…

C++操作excel,即使函数设置了不备份,但保存后,excel依然会自动生成备份文件的原因分析,及如何来禁止自动备份

开发环境 操作系统:windows 10 编译器:Visual Studio 2010、2015、2017、2022 office 2016、2019、2021 wps 2019、2024 问题描述 通过C操作excel,保存后,excel会自动生成备份文件。 void CExcelDemoDlg::OnBnClickedButton1() …

Open3D mesh 隐藏点移除

目录 一、概述 1.1原理 1.2实现步骤 1.3应用场景 二、代码实现 2.1关键函数 2.2完整代码 三、实现效果 3.1原始点云 3.2去除隐藏点后的点云 Open3D点云算法汇总及实战案例汇总的目录地址: Open3D点云算法与点云深度学习案例汇总(长期更新&…

力扣 128. 最长连续序列

题目描述 我的思路 我的思路比较暴力,就是首先将数组从小到大进行排序,然后再依次遍历判断序列是否连续并时时更新连续序列的最长长度。比如示例1:nums [100, 4, 200, 1, 3, 2],第一步先将数组进行排序得到sort_nums [1, 2, 3,…

Android Studio(3) 使用 Kotlin DSL和 Gradle 8.7 打包远程库到 AAR 的自定义方法

背景介绍 在 Gradle 7.3 及更早版本中,通常使用 com.kezong.fat-aar 插件来打包远程库到 AAR 中,随着 Gradle 的不断升级,尤其是到 8.7 版本后,Kotlin DSL开发逐渐成为主流,fat-aar 社区没有更新,插件的兼容性问题逐渐显现。我探索一种新的自定义方法,能够在 Kotlin DS…

js逆向学习

目前本人大三下,想要学习js逆向同学的可以联系我:2697279763qq.com 上面是本人做的一些比较复杂的项目,还有很多简单的项目,这里给出图片。 还有一些简单的js逆向。 教你各种补充环境,各种js算法,教你各种底…

(软工) 四代软件架构

🔢前言 当今软件架构中,拥有四代软件架构。这四个都是基于所在时代,技术,需求等多种因素应运而生的。 在未来是否会有第五代软件架构,无人可知。笔者大胆推测,这第五代很可能与人工智能的大语言模型有关&…

Windows—UDP编程

Client骨架&#xff1a; #include <iostream> #include <WinSock2.h> #pragma comment(lib,"ws2_32.lib")int main() {//启动Winsock DLLWORD wVersionRequested MAKEWORD(2, 2);WSADATA lpWSAData;WSAStartup(wVersionRequested, &lpWSAData);//…

【数据结构】线性表的顺序表示(顺序表的定义和基本操作)

计算机考研408-数据结构笔记本之——第二章 线性表 2.2 线性表的顺序表示&#xff08;顺序表的定义和基本操作&#xff1a;初始化/插入/删除/查找&#xff09; 2.2.1 顺序表的定义 1.定义 顺序表是线性表的顺序存储。 所谓顺序存储&#xff0c;就是把逻辑上相邻的元素存储在物…

预约咨询小程序搭建开发,uniapp前端,PHP语言开发

目录 前言&#xff1a; 一、预约小程序搭建功能介绍 二、示例代码片段 前言&#xff1a; 预约咨询小程序适合需付费咨询和交流的场景&#xff1a;比如讲师,摄影,婚庆&#xff0c;美发,律师,心理等等支持商家入驻支持视频、图文、线下、电话等方式在线支付咨询。 一、预约小程…

代码随想录 刷题记录-14 回溯(3)字符串、子集、排列问题

字符串 1.131.分割回文串 思路 本题这涉及到两个关键问题&#xff1a; 切割问题&#xff0c;有不同的切割方式判断回文 切割问题&#xff0c;也可以抽象为一棵树形结构&#xff0c;如图&#xff1a; 回溯三部曲 递归函数参数 全局变量数组path存放切割后回文的子串&…

《计算机操作系统》(第4版)第5章 虚拟存储器 复习笔记

第5章 虚拟存储器 一 、虚拟存储器概述 1. 常规存储管理方式的特征和局部性原理 (1)特征 ①一次性。 ②驻留性。 (2)局部性原理 局部性原理表现在时间局部性和空间局部性两方面。 2.虚拟存储器的定义和特征 (1)虚拟存储器的定义 虚拟存储器是指具有请求调入功能和置换功能&…

java之表格数据存储

java之表格数据存储 摘要表格数据存储javabean 介绍javabean 设计类表格数据存储 摘要 本博客主要讲述java如何存储表格数据。 表格数据存储 在解决实际问题的时候&#xff0c;需要涉及到如何存储表格的数据&#xff0c;这里讲述了一种使用javabean的方法存储表格 javabea…

STM中的I2C

常见的几种通信接口 I2C总线定义 定义 I2C - Inter-Integrated Circuit&#xff1a;两线式 串行总线&#xff1a;说明处理器和外设之间只需两根信号线&#xff0c;分别是SCL时钟控制信号线和SDA数据线 SCL&#xff08;serial clock line&#xff09; 时钟控制信号线&#xff…

makefile文件基本语法

一、makefile文件基本介绍 Makefile 文件是 make 工具使用的配置文件&#xff0c;它定义了如何自动化构建项目的规则和命令。Makefile 文件的主要作用是指定如何编译和链接程序&#xff0c;以及管理文件之间的依赖关系&#xff0c;从而实现高效的构建过程。 1.1 Makefile 的基…

【FreeRTOS】队列实验-分发数据给多个任务(赛车游戏)

目录 0 前言1 队列实验_分发数据给多个任务(赛车游戏)2 赛车游戏2.1 game.c2.2 注册队列2.3显示汽车2.4隐藏汽车2.5 CarTask2.6 car_game2.7 MX_FREERTOS_Init 3 总结 0 前言 学习视频&#xff1a; 【FreeRTOS入门与工程实践 --由浅入深带你学习FreeRTOS&#xff08;FreeRTOS教…

如何用Python实现山东省旅游数据爬虫与K-means满意度分析

&#x1f393; 作者&#xff1a;计算机毕设小月哥 | 软件开发专家 &#x1f5a5;️ 简介&#xff1a;8年计算机软件程序开发经验。精通Java、Python、微信小程序、安卓、大数据、PHP、.NET|C#、Golang等技术栈。 &#x1f6e0;️ 专业服务 &#x1f6e0;️ 需求定制化开发源码提…

AI一键视频多语言配音/翻译工具:打造无缝多语言视频体验

在全球化的今天,视频内容的传播不再受限于地域和语言。然而,如何高效地将视频内容翻译成多种语言并保持其自然度和流畅性,一直是业界面临的挑战。为了解决这一难题,我们推出了一款智能视频多语言AI配音和翻译工具——Linly Dubbing。该工具基于YouDub-webui的灵感进行了创新…

开源:cuda studio云原生一站机器学习、深度学习、大模型AI平台

文章目录 1、 cuda studio云原生一站机器学习、深度学习、大模型AI平台2、网址 1、 cuda studio云原生一站机器学习、深度学习、大模型AI平台 cube studio开源云原生一站式机器学习/深度学习/大模型AI平台&#xff0c;支持sso登录&#xff0c;多租户&#xff0c;大数据平台对接…