Serial 适用于单核 CPU 的环境,ParNew 适用于多核 CPU 的环境,而 Parallel Scavenge 则适用于响应时间不是很重要的应用场景。但是这些垃圾回收器也有自己的限制条件,比如 Serial 和 Parallel Scavenge 在多线程环境下效率不高,而 ParNew 的停顿时间相对较长。针对这些问题,可以考虑使用其他垃圾回收器,如 CMS 或 G1,根据具体业务场景选择最适合的垃圾回收器。
🎉 Serial
Serial是一种单线程运行的垃圾回收器,它使用复制算法,适用于单核 CPU 的环境。在工作时,Serial 会将所有应用线程全部冻结,因此不适合对响应时间敏感的系统。由于只有单线程在工作,所以执行步骤很简单,不需要线程交互开销,专注于垃圾回收。但是,由于只能单线程进行垃圾回收,所以在多核处理器上表现不佳,基本不适用于多线程环境。
🎉 ParNew
ParNew是新生代下的多线程垃圾回收器,也使用复制算法,适用于多核 CPU 的环境。与 Serial 相比,ParNew 可以并行 GC,因此性能更高。它支持多线程回收,减少了垃圾回收的时间,不会将所有应用线程全部冻结,因此不会对响应时间造成影响。ParNew 的执行步骤与 Serial 相同,但是在多线程环境下,可以同时执行多个线程的垃圾回收,因此速度更快。
🎉 Parallel scavenge
Parallel Scavenge 是新生代下的多线程垃圾回收器,也使用复制算法,可以进行吞吐量控制。它支持并行并发 GC,适用于响应时间不是很重要的应用场景。Parallel Scavenge 关注系统吞吐量,通过控制停顿时间来适应不同的场景。它会将 CPU 利用率最大化,达到较高的吞吐量。但是,和 ParNew 相比,它的停顿时间相对较长,可能会对响应时间造成影响。
🎉 复制算法
复制算法是一种非常常用的垃圾回收算法,它的原理可以简单的概括为将存活对象从一个内存区域复制到另一个内存区域。
通常情况下,复制算法被用于新生代的内存回收,因为新生代中的对象生命周期通常都比较短,而且新生代中的大部分对象在一轮垃圾回收后就会被清理掉,所以复制算法非常适合用来处理新生代中的垃圾回收。
具体来说,复制算法会将新生代内存分为两个大小相等的区域,通常称之为“from space”和“to space”。在开始进行垃圾回收之前,所有的新对象都会被分配到“from space”中。
每次进行垃圾回收的时候,复制算法会扫描“from space”中所有的对象,并将所有存活的对象复制到“to space”中。复制的过程是将存活对象依次复制到“to space”中,复制完成后,“from space”中就没有存活的对象了。同时,“to space”中的对象也是可用的,而“from space”则可以被清空,用作下一轮的垃圾回收。
需要注意的是,当“to space”中的内存空间也被占满的时候,就需要将所有存活的对象从“to space”中复制回“from space”,然后再将“to space”清空,用于下一轮的垃圾回收。
整个复制过程看起来可能比较消耗时间和空间,但是实际上,由于新生代中的对象通常都比较小,而且新生代中的垃圾回收频率也比较高,所以复制算法在新生代中的表现非常优秀,能够有效地减少内存碎片化问题,并且在回收效率上也有很好的表现。
举例来说,假设我们有一块内存区域,大小为4个单位,这个内存区域被分成两个大小相等的“from space”和“to space”,两个区域都为空。
现在我们分配了3个对象A、B和C,其中A和B被分配到了“from space”中,而C被分配到了“to space”中。此时“from space”中有2个对象,而“to space”中有1个对象。
接下来进行垃圾回收,扫描“from space”后发现只有对象A和对象B存活,所以将它们复制到“to space”中。复制后,“to space”中就有对象A和对象B了。同时,“from space”中的对象也可以被清空,用于下一轮的垃圾回收。
下一轮分配对象的时候,对象D被分配到了“from space”中,此时“from space”中有1个对象,而“to space”中有2个对象。
当“to space”中的内存空间被占满时,我们需要将存活的对象从“to space”中复制回“from space”,然后再将“to space”清空。这个过程中,我们需要复制对象A和B回到“from space”中。
整个过程中,“from space”和“to space”不断地被交替使用,垃圾回收的效果非常好,并且内存碎片化问题也可以得到很好的解决。
🎉 分代收集算法
分代收集算法是一种垃圾回收算法,其主要思想是将堆空间划分为不同的代,以便能够针对不同代的特点来采取不同的回收策略,从而提高垃圾回收的效率和性能。
在Java应用程序中,通常将堆空间划分为年轻代(Young Generation)、老年代(Old Generation)和永久代(Permanent Generation),其中年轻代又被划分为Eden区和两个Survivor存活区,比例通常为8:1:1。
当应用程序向堆空间申请内存时,会先从年轻代中分配。当Eden区满后,会触发Minor GC(年轻代垃圾回收),此时,会将Eden区和其中一个Survivor区中存活的对象复制到另一个空的Survivor区中,并将原来的Survivor区和Eden区清空。如果Survivor区也被填满了,那么这些对象会被复制到老年代中,而在复制过程中,会进行标记清除算法,将没有被引用的对象进行清除。
当老年代中的空间也被填满后,会触发Major GC(老年代垃圾回收),此时,会对整个堆空间进行垃圾回收,其中,会先标记出所有存活的对象,再将没有被标记的对象进行清除。由于Major GC需要扫描整个堆空间,因此其效率相对较低,而Minor GC则仅针对年轻代进行回收,因此效率较高。
在永久代中存储的是一些静态的数据,如类信息、方法信息等,一般不会产生太多垃圾,因此垃圾回收也相对较少。但是,由于永久代的大小是固定的,并且永久代中存储的数据是不能被回收的,因此在应用程序长时间运行后,可能会出现永久代溢出的情况。
总的来说,分代收集算法可以提高垃圾回收的效率和性能,同时也能够避免出现长时间的停顿和内存溢出等问题。
🎉 进入老年代的几种情况
首先,当一个对象在Survivor区域中经历了一次GC(垃圾回收),并且仍然存活,那么它的年龄会加1。这是因为在Survivor区中,对象会不停地往返移动,当它的年龄达到一定阈值(默认为15岁)时,它将被移动到老年代中。这就是第一种情况。
第二种情况涉及到一个参数叫做“max tenuring threshold”,它是JVM中的一个参数,用于控制何时将对象从年轻代移到老年代。如果要创建一个很大的对象,其大小超过了这个参数的值,那么这个对象将直接被分配到老年代。
第三种情况是在Survivor区里面,同一年龄的所有对象大小的总和大于Survivor区大小的一半,年龄大于等于这个年龄对象的,就可以直接进入老年代,举个例子,存活区只能容纳5个对象,有五个对象,1岁,2岁,2岁,2岁,3岁,3个2岁的对象占了存活区空间的5分之三,大于这个空间的一半了,这个时候大于等于2岁的对象,需要移动到老年代里面,也就是3个2岁的,一个3岁的对象移动到老年代里面。
最后,第四种情况是当Eden区中存活的对象占用的空间超过了Eden区的大小时,这些对象将被直接移动到老年代中。
🎉 空间分配担保策略
除了以上情况外,还有一种情况:在进行Minor GC之前,JVM会检查老年代最大可用连续空间是否大于新生代所有对象的总空间。如果大于,那么这次Minor GC就是安全的。否则,JVM会检查“HandlePromotionFailure”这个值,如果它是true,则表示运行担保失败,如果是false,则表示不允许担保失败。如果允许担保失败,那么JVM会检查老年代最大可用连续空间是否大于历次晋升到老年代平均对象大小。如果是,那么JVM会尝试进行一次有风险的Minor GC。如果不是,或者不允许担保失败,那么JVM将直接进行Full GC。
举个例子,在minorgc发生之前,年轻代里面有1g的对象,这个时候,老年代瑟瑟发抖,jvm为了安慰这个老年代,它在minor gc之前,检查一下老年代最大可用连续空间,假设老年代最大可用连续空间是2g,jvm就会拍拍老年代的肩膀说,放心,哪怕年轻代里面这1g的对象全部给你,你也吃的下,你的空间非常充足,这个时候,老年代就放心了。
但是大部分情况下,在minor gc发生之前,jvm检查完老年代最大可用连续空间以后,发现只有500M,这个时候虚拟机不会直接告诉老年代你的空间不够,这个时候会进行第二次检查,检查自己的一个参数handlepromotionfailure
的值是不是允许担保失败,如果允许担保失败,就进行第三次检查。
检查老年代最大可用连续空间是不是大于历次晋升到老年代平均对象大小,假设历次晋升到老年代平均对象大小是300M,现在老年代最大可用连续空间只有500M,很明显是大于的,那么它会进行一次有风险的minorgc,如果gc之后还是大于500M,那么就会引发fgc了,但是根据以往的一些经验,问题不大,这个就是允许担保失败。
假设历次晋升到老年代平均对象大小是700M,现在老年代最大可用连续空间只有500M,很明显是小于的,minorgc风险太大,这个时候就直接进行fgc了,这就是我们所说的空间分配担保。
🎉 JVM分代年龄为什么是15次?
JVM分代是为了更好地管理Java对象的内存分配和回收。Java对象可以分为新生代和老年代两种,新生代又可以分为Eden区、Survivor区1和Survivor区2。大多数Java对象在创建后都会被分配到新生代的Eden区,经过一次Minor GC后,存活的对象会被移动到Survivor区1或Survivor区2。当Survivor区1或Survivor区2满了之后,会触发一次Minor GC,并把还存活的对象移动到另一个Survivor区。当一个对象从新生代被移动到老年代时,就需要考虑其年龄。
Java对象的GC年龄是指该对象在新生代中经历过的Minor GC的次数。每次Minor GC后,如果该对象没有被回收,它的年龄加1,直到达到一个阈值,默认是15次,也可以通过参数来设置。当对象的年龄达到阈值时,就会被移动到老年代。
为什么设置阈值为15次呢?
这主要是出于性能和可靠性的考虑。首先,设置较小的阈值可能会导致频繁的Minor GC,会影响系统的性能。其次,如果阈值设置得太大,那么一些不再使用的对象可能会一直存在于新生代中,占用宝贵的空间,影响系统的可靠性。因此,15次应该是一个比较合理的阈值。
举个例子来说明,如果我们把阈值设置为10次,那么每次Minor GC后,所有年龄小于10的对象都会被移动到下一个Survivor区或老年代中,这可能会导致Survivor区的空间不足,进而会影响系统的性能。如果我们把阈值设置为20次,那么一些不再使用的对象可能会一直存在于新生代中,占用宝贵的空间,进而会影响系统的可靠性。
除了上述原因以外,还有一个重要因素是对象头里面有4个bit位来存储GC年龄,4个bit位能够存储的最大数值是15。
一个对象的GC年龄,是存储在对象头里面的,一个Java对象在JVM内存中的布局由三个部分组成,分别是对象头、实例数据、对齐填充。而对象头里面有4个bit位来存储GC年龄。
4个bit位能够存储的最大数值是15,所以从这个角度来说,JVM分代年龄之所以设置成15次是因为它最大能够存储的数值就是15。虽然JVM提供了参数来设置分代年龄的大小,但是这个大小不能超过15。从设计角度来看,当一个对象触发了最大值15次gc,还没有办法被回收,就只能移动到old generation了。另外,设计者还引入了动态对象年龄判断的方式来决定把对象转移到old generation,也就是说不管这个对象的gc年龄是否达到了15次,只要满足动态年龄判断的依据,也同样会转移到old generation。
🎉 Serial Old
什么是Serial Old垃圾回收器呢?它是Java虚拟机(JVM)中的一种垃圾回收器,专门用于清理老年代的对象。老年代是一个存储已经存在一段时间的对象的区域,通常是堆中的一部分。Serial Old垃圾回收器使用的是标记整理算法,这意味着它会首先对堆进行标记,然后整理出可用空间。
值得注意的是,Serial Old垃圾回收器是单线程运行的。这意味着在垃圾回收期间,所有其他的线程都将暂停。因此,使用Serial Old垃圾回收器会导致停顿时间较长,这可能会对应用程序的性能产生负面影响。
🎉 Parallel old
Parallel old垃圾回收器是为了解决Serial Old垃圾回收器的这个问题而出现的。Parallel old垃圾回收器也是一种标记整理算法垃圾回收器,但与Serial Old不同的是,它是多线程的,可以通过控制线程数来加快垃圾回收的速度。因此,使用Parallel old垃圾回收器可以显著减少停顿时间,并提高应用程序的吞吐量。
需要注意的是,在JDK1.6之前,如果使用ParallelScavenge收集器收集新生代,就必须使用Serial Old收集器收集老年代。这意味着无法完全保证整个应用程序的吞吐量。但是,自JDK1.6以来,Parallel old垃圾回收器已经作为一个独立的收集器呈现,它可以与ParallelScavenge收集器一起使用,从而为整个应用程序提供高吞吐量。
上面的Serial Old,Parallel Old这二个垃圾回收器使用的是标记整理算法.
🎉 标记整理算法
标记整理算法是一种内存回收算法,其主要思路是在标记清除算法的基础上进行优化。该算法将标记后的存活对象移向内存的一端,然后清除端边界外的对象。这样可以消除内存碎片,避免出现大量不连续的空闲内存块,从而保证后续的内存分配能够连续进行。
整个算法可以分为两个步骤:标记和整理。
📝 1. 标记
标记阶段与标记清除算法类似,遍历所有的对象,标记出所有的存活对象。标记的过程可以使用深度优先搜索或广度优先搜索进行优化。
📝 2. 整理
在标记完成后,将所有存活对象向内存的一端移动,使它们之间没有空隙。同时清除端边界外的对象,这样就可以避免内存碎片带来的问题。整理后,内存地址连续,可以直接进行后续的内存分配。
与复制算法相比,标记整理算法无需拷贝存活对象,因此不会浪费一半的内存空间,但是需要对所有存活对象的引用地址进行整理,效率相对较低。
以下是示例代码:
// 标记阶段 // 标记对象obj,如果对象已经被标记了则直接返回 void mark(Object obj) { if (obj.marked) return; obj.marked = true; // 遍历对象obj的所有字段 for (int i = 0; i < obj.numFields; i++) { mark(obj.fields[i]); } } // 整理阶段 // 释放未被标记的对象的空间 void sweep() { Object obj = firstObject; while (obj != null) { if (!obj.marked) { // 将未被标记的对象移除,并释放其空间 Object unreached = obj; obj = unreached.next; free(unreached); } else { // 将已经被标记过的对象重新标记为未被标记 obj.marked = false; obj = obj.next; } } } // GC入口 // 执行垃圾回收 void collect() { // 标记所有活动对象 markAll(); // 整理并释放未被标记的对象的空间 sweep(); }
标记整理算法可以弥补标记清除算法的缺点,消除内存碎片,提高内存使用率。但是效率相对较低,需要对所有存活对象的引用地址进行整理。在实际应用中,需要根据具体场景选择合适的内存回收算法。
🎉 CMS
CMS(Concurrent Mark Sweep)是Java虚拟机中的一种垃圾回收器,用于对老年代进行垃圾回收。它采用标记清除算法,并且具有低停顿和多线程特性,因此被广泛应用于高并发的Java应用程序中。
CMS垃圾回收器的工作可以类比为餐厅的服务员清理桌子上的脏盘子。在餐厅中,如果服务员等到所有的顾客用完了之后再一起清理,那么就会导致餐厅非常脏乱,需要花费很长的时间来清理。因此,服务员需要时不时地清理桌子上的脏盘子,以保持餐厅的整洁。同样地,CMS垃圾回收器会在工作过程中不断地标记、清理垃圾对象,以保持JVM内存的整洁。
具体来说,CMS垃圾回收器的工作分为以下4个阶段:
📝 1. 初始标记阶段
在这个阶段,垃圾回收器会暂停所有的工作线程,只是标记一下GC Roots,以及GC Roots能直接关联的对象。这个过程速度很快,通常只需要几毫秒的时间。
📝 2. 并发标记阶段
在这个阶段,垃圾回收器会跟踪GC Roots,并且标记所有与GC Roots直接或间接关联的对象。这个过程是和用户线程一起工作的,不需要暂停工作线程,因此称为并发标记。
📝 3. 重新标记阶段
在并发标记的过程中,可能因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录需要重新标记,在这个阶段,垃圾回收器需要暂停所有的工作线程,以修正标记记录。
📝 4. 并发清除阶段
在这个阶段,垃圾回收器会清除所有GC Roots不可达的对象,这个过程也是和用户线程一起工作的,不需要暂停工作线程,因此称为并发清除。
需要注意的是,在并发标记和并发清除的过程中,GC线程和用户程序是同时运行的。如果两者产生竞争,就可能导致GC线程不得不暂停手头的工作,以便等待用户线程完成,这也是CMS回收器无法完全避免GC带来的停顿时间的原因。
此外,CMS回收器可能会产生内存碎片的问题,这是由于它采用的是标记清除算法,会在内存中留下大量不连续的空闲块。当需要分配较大对象时,就可能出现无法找到连续空闲空间的情况。这个问题可以通过使用G1垃圾回收器等先进的回收算法来解决。
CMS使用标记清除算法看中的就是它的效率高,只不过内存碎片化严重,后续可能发生大对象不能找到可利用空间的问题。
🎉 标记清除算法
想象一下这样的场景:
你正在整理家里的房间,你发现这里有很多垃圾和不再需要的东西,但是你又不确定哪些东西是需要丢弃的。于是你开始一个个检查所有的物品,然后在不需要的物品上做上标记,最后你从房间里把所有带有标记的物品清理出去,这就是标记清除算法的大致流程。
在计算机领域,标记清除算法是一种用来回收不再使用的内存空间的算法。这种算法分为两个主要阶段:标记阶段和清除阶段。
在标记阶段,程序会遍历所有的对象,并对所有需要回收的对象做上标记。不需要回收的对象则不做处理。这个过程类似于整理房间时检查物品,找出哪些是需要清理的。
在清除阶段,程序通过遍历所有的内存空间,找到所有被标记的对象并将其所占用的内存空间释放出来。这个过程类似于从房间中清理出带有标记的物品。
标记清除算法相对于其他内存回收算法来说具有较高的效率和可用性。但它也有一些不足之处,例如可能会产生"碎片",即留下许多无法再次利用的小块内存空间,在程序运行时间较长的情况下,这会导致内存空间的浪费。
总之,标记清除算法是一种非常重要的内存回收算法,它可以帮助程序更好地管理内存空间,保持程序的稳定性和性能。
🎉 G1
G1(Garbage-First)收集器是Java虚拟机的一种垃圾收集器,采用了分代理论,将堆内存划分为大小固定的几个独立区域,每个区域又可以根据分代理论分为Eden区、Survivor区和Old区。其目的是为了避免全区域垃圾收集,从而在有限时间内获得最高的垃圾收集效率。
当一个对象被创建时,会被分配在Eden区。当Eden区满时,会触发一次Minor GC,将Eden区和Survivor区中的存活对象拷贝到另一个Survivor区,并清空该区域。如果还有存活对象,就会进入下一次Young GC。若Survivor区中的对象经过多次Survivor GC后仍然存活,就会晋升到Old区。
另外,如果一个区域里面出现了一个对象,超过了该区域空间的一半,就可以将其当作大对象。G1专门开辟了一块空间用来存储大对象,这个区域的大小可以通过JVM的参数进行设置,取值范围是1~32MB之间。如果有一个对象超过了32MB,那么JVM会分配二个连续的区域,用来存储这个大对象。
G1收集器采用标记整理算法,不产生内存碎片。它可以非常精确控制停顿时间,在不牺牲吞吐量的前提下,实现低停顿垃圾回收。G1收集器可以跟踪这些区域的垃圾收集进度,同时在后台维护一个优先级列表,每次根据所允许的收集时间,优先回收垃圾最多的区域。区域划分和优先级区域回收机制保证了G1收集器的高效性和性能稳定性。
在JDK1.9版本中,G1收集器被设置成默认的垃圾回收器。因为G1收集器在垃圾回收效率和系统稳定性方面有很大的优势,因此越来越多的应用程序开始采用G1收集器。
🎉 ZGC
ZGC 是 JDK11 中的一种垃圾回收器,它的主要功能是对 Java 应用程序中产生的垃圾进行清理,以便腾出更多的内存空间供应用程序使用。与其他垃圾回收器不同之处在于,它的停顿时间非常短,只有不到 10 毫秒,这使得它可以在不影响应用程序性能的情况下进行垃圾回收。
ZGC 的一个重要特点是,其停顿时间不会因堆变大而变长。这是因为 ZGC 采用了一种分代垃圾回收的策略:将堆空间划分为多个代,每个代独立进行垃圾回收。随着堆空间变大,ZGC 会自动增加代的数量,从而实现对更大堆空间的支持。
ZGC 支持 8MB ~ 4TB 级别的堆,这使得它可以适用于各种规模的 Java 应用程序。此外,ZGC 还具有动态调整线程数量、并发标记和并发整理等功能,这些功能都可以帮助应用程序更好地利用内存资源,提高程序的性能。
📝 内存结构
ZGC 的内存结构是按照页为单位进行划分的,其中小型 Region 的容量固定为 2MB,用于放置小于 256KB 的小对象;中型 Region 的容量固定为 32MB,用于放置大于 256KB 但小于 4MB 的对象;大型 Region 的容量不固定,可以动态变化,但必须为 2MB 的整数倍,用于放置 4MB 或以上的大对象。
📝 回收过程
下面我们来详细了解一下ZGC的三个STW阶段:
🔥 1. 初始标记(Initial Marking)
在这个阶段中,ZGC会扫描整个堆,标记出所有活动的对象。这个过程需要STW,因为在这个阶段中,GC算法需要扫描Root集合,即全局可达对象集合,并标记为活动对象,同时标记所有的引用对象以避免被回收。
例如,当一个对象的引用已经被另一个对象取代了,初始标记就会标记这个对象为待回收对象。这个阶段通常只需要几毫秒的时间。
🔥 2. 再标记(Concurrent Marking)
在标记所有存活对象之后,ZGC会在堆中标记新对象的一些变化,并使用多线程进行GC。这个阶段也需要STW,因为GC算法需要遍历存活对象的引用字段,并标记丢弃的对象。这个阶段通常持续几毫秒而已。
例如,当一个对象的引用被另一个活动对象取代时,再标记阶段就会标记这个对象为已删除的对象。这个阶段的目的是标记所有可能被回收的对象,并准备好将它们从堆中删除,以便在下一个阶段中进行内存整理。
🔥 3. 初始转移(Initial Relocate)
这个阶段是ZGC的独特之处。在所有存活对象都被标记后,ZGC会开始对堆中的对象进行移动和重构。这个过程是并发的,不需要STW。
在这个阶段中,ZGC会将活动对象移动到一个新的区域中,并释放已删除的对象的内存。这将导致堆对象的布局和连续性发生变化。这个阶段的目的是重新组织堆中的对象,使其更加有序和紧凑,并减少碎片化,以便更高效地使用内存。
例如,当对象被移动到新的区域时,它们的引用会被更新,以指向它们在新区域中的位置。ZGC一般会在多个轮次中执行这个阶段,以便在每个轮次中移动一小部分对象,以减少任何一次移动的影响。
在整个ZGC的运行过程中,这三个STW阶段共同协作,使得ZGC能够在几毫秒内进行高效的垃圾回收操作,不会过多地影响应用程序的性能。这种方式使得ZGC非常适用于需要高可用性和低延迟的应用程序。
STW指的是Stop-The-World,即停顿整个程序世界的运行。在Java虚拟机中,STW一般用于垃圾回收、内存分配器的运行等等需要整个虚拟机运行环境停止的场景中。在STW期间,所有的线程都会暂停执行,直到垃圾回收等操作完成。由于STW会导致系统停顿,因此一般需要尽量缩短STW的时间以提升应用程序性能。
📝 技术特性
🔥 读屏障
ZGC中的读屏障机制可以保证线程读取的对象都是有效的,避免了出现空指针等错误。例如,假设一个线程在读取一个对象时,垃圾回收器正好在回收该对象,如果没有读屏障机制的话,该线程可能会读取到内存中的垃圾数据,导致程序出现错误。而ZGC中的读屏障机制会阻塞该线程,直到垃圾回收完成,确保线程读取的是有效的对象。
🔥 着色指针
着色指针是ZGC中另一个重要的机制,它可以标记对象是否存活。当一个对象被标记为黑色时,表示它是存活的;而未被标记的白色对象则表示它们需要被回收。在垃圾回收器执行时,它会从根对象开始遍历所有可达对象,并将它们标记为黑色。同时,为了提高垃圾回收的效率,ZGC还使用灰色和原色两个状态,使得垃圾回收器可以更快速地确定哪些对象需要被回收。
🔥 并发标记和并发清除
ZGC使用了并发标记和并发清除的方式进行垃圾回收,这意味着垃圾回收器和应用程序可以同时运行,从而避免了长时间停顿的情况。与传统的垃圾回收器相比,ZGC的停顿时间大大减少,从而提高了应用程序的响应速度和可用性。
🔥 动态地调整堆的大小
ZGC还可以动态地调整堆的大小,以便根据应用程序的需求来优化性能。它可以处理非常大的内存堆,比如几十GB或甚至上百GB的堆,这使得ZGC非常适合处理大型Java应用程序,例如大型数据库或内存密集型的应用程序。