JVM垃圾回收

垃圾回收(GC)的范围

根据JVM的内存划分,线程私有的区域(虚拟机栈、本地方法栈、程序计数器),其生命周期都和线程相关,随着线程的终止,相关内存最终都会被释放掉。相对于线程共享区域,线程私有区域的内存回收是明确可控的。因此,垃圾回收器的主要工作范围就在线程共享的方法区和堆,其中堆是垃圾回收的重点区域。

如何确定垃圾

常见的方法有"引用计数法"和"可达性分析"。

引用计数法:
为对象添加一个引用计数器,对象每被引用一次,计数加1,每断开一个引用,计数减1;如果一个对象的引用计数为0,则表示为垃圾,可以回收。
引用计数法的主要问题:无法解决循环依赖的问题,即对象A和对象B之间相互引用,此外没有任何引用再指向这两个对象,此时这两个对象都应该是可回收的,但实际上它们的引用计数器却都是1,无法判定为可回收。

可达性分析:
可达性分析是JVM所采用的方法,从"GC Roots"直接关联的对象开始,沿着引用链向下搜索,能够追踪到的对象,被认为是存活的;而不可达对象,在至少经历两次标记之后,如果仍不可达,则被判定为可回收的。
"GC Roots"直接关联的对象,主要包含:
1、虚拟机栈中本地变量引用的对象
2、方法区中类的静态变量引用的对象
3、方法区中常量引用的对象
4、本地方法栈中JNI引用的对象。

常见的垃圾回收算法

1、标记清除算法(Mark-Sweep)
分为标记和清除两个阶段,先标记存活对象,然后清除可回收对象释放内存。
缺点:内存碎片化。
在这里插入图片描述
2、复制算法(Copying)
将内存分为大小相等的两个区域,保证同一时刻只有一个内存区域可用。
垃圾回收时,标记后存活的对象被全部复制到另一块空的内存区域,然后直接清空之前使用过的内存区域。
优点:效率高。缺点:空间浪费。
在这里插入图片描述
3、标记整理算法(Mark-Compact)
将标记完成后存活的对象移动到内存区域的一端,然后清理掉对象边界外的另外一端。
优点:无碎片化、空间利用率高。缺点:效率低。
在这里插入图片描述

方法区垃圾回收

方法区垃圾回收的对象主要有两种:
1、废弃的常量,即没有任何地方引用到的常量;
2、废弃的类,通常具备以下三个条件:

  • 该类的所有实例都已被回收;
  • 该类的类加载器已经被回收;
  • 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类。

方法区的GC此处仅做了解,后续内容基于堆的GC进行展开。

堆垃圾回收

分代回收思想
具体到JVM堆中,按照堆中对象的存活周期,将堆内存分为了年轻代、老年代两个区域,默认空间占比为1:2;
年轻代主要存储生命周期较短,或者体积较小的对象。年轻代垃圾回收时通常只有少量的存活对象,而可回收的对象较多,所以适合用效率较高的复制算法进行回收。
老年代主要存储生命周期较长的对象和大对象。老年代垃圾回收时可回收对象较少,多采用标记清除或者标记整理算法进行回收。

年轻代垃圾回收
年轻代GC,称为MinorGC或YoungGC,采用复制算法实现。
年轻代分为3个区域:一个Eden区(伊甸园区)、两个相同的Survivor区(幸存者区,可以起名SurvivorFrom、SurvivorTo以示区别),默认空间占比为8:1:1。
Java新建的对象首先放入Eden区(如果属于大对象,则不会进入Eden区,而是直接放入老年代),Eden区空间不足时,会触发MinorGC。

MinorGC过程:
首次GC:两个Survivor区是空的,只有Eden区有存活对象;将Eden区中的存活对象复制到其中一个Survivor区,然后清理Eden区。
非首次GC:
1、将Eden区、SurvivorFrom区中的存活对象复制到SurvivorTo区,然后清理Eden区、SurvivorFrom区;
2、将SurvivorFrom区、SurvivorTo区互换,即将上次回收清空的Survivor区作为下次回收的对象存放区。

对象进入老年代的方式:
1、大对象创建后直接分配到老年代,大对象阈值可通过JVM参数-XX:PretenureSizeThreshold设置,默认值为0,表示全部进入年轻代Eden区。
2、年轻代中长期存活的对象,会被转移到老年代。
对象每经历一次MinorGC并且在Survivor区中存活下来,其年龄会+1;如果经历多次GC后,年龄达到晋升老年代的阈值,则会被转移到老年代中。年龄阈值默认为15,可通过参数-XX:MaxTenuringThreshold进行配置。
3、动态对象年龄判断。
MinorGC后,SurvivorTo区中从年龄为1的对象开始,计算年龄为1的对象总大小,然后加上年龄为2的对象总大小,一直累加到年龄为n的对象总大小;当年龄1到n的对象占用总空间大于当前SurvivorTo区空间的50%时,年龄大于等于n的对象被转移到老年代。
50%空间占用率为默认值,可通过参数-XX:TargetSurvivorRatio进行设置。
4、年轻代GC时SurvivorTo区空间不足。
MinorGC时会将存活对象复制到SurvivorTo区,如果此时SurvivorTo区空间不足,则直接将存活对象放入老年代。

老年代空间分配担保机制
MinorGC时可能会有对象进入到老年代,但是老年代也有可能空间不足,此时就会触发FullGC。
如果可以提前计算出老年代剩余空间和年轻代对象大小的关系,是不是就可以提前预知本次MinorGC的风险,选择合适的GC策略,节约时间成本呢?答案是可以。
老年代空间分配担保机制(以JDK 6 Update 24之后的新规则为例),就是在MinorGC之前,判断老年代的最大可用连续空间,如果大于年轻代所有对象总大小或者历次晋升老年代对象的平均大小,就会进行MinorGC,否则进行FullGC。
JDK 6 Update 24之前的旧规则相对复杂些,还需要结合参数-XX:HandlePromotionFailure(是否允许担保失败)来判断。

老年代垃圾回收
老年代空间不足时,会触发FullGC。
FullGC:对整个堆(年轻代和老年代)以及方法区进行垃圾回收。
老年代的GC,也叫MajorGC或OldGC,通常来讲MajorGC和FullGC没有明确定义上的区分,可以认为是等价的,因为目前只有CMS垃圾回收器有单独回收老年代的行为,其他的MajorGC往往进行的也是整堆回收。

FullGC的触发条件:
1、调用System.gc()方法,建议系统进行FullGC,但不一定会执行;
2、老年代空间不足,比如大对象创建、MinorGC时对象进入、MinorGC前空间分配担保判定空间不足等;
3、方法区空间不足。

stop the world
不管是YGC还是FullGC,JVM在垃圾回收时都会暂停所有的用户线程,避免创建对象对GC产生干扰,也就是说我们的业务代码此时无法响应用户请求,这段时间仿佛世界停止了(stop the world),等待GC完毕用户线程才能恢复。

常见的垃圾回收器

基于堆的分代回收思想,年轻代和老年代使用了不同的垃圾回收算法,所以针对年轻代和老年代使用的垃圾回收器也不同。
年轻代垃圾回收器
1、Serial垃圾回收器,单线程,复制算法
Serial垃圾回收器是JVM运行在客户端模式下的默认年轻代垃圾回收器。
2、ParNew垃圾回收器,多线程,复制算法
ParNew垃圾回收器是Serial垃圾回收器的多线程实现,默认开始与CPU数量相等的线程进行垃圾回收,也可通过参数-XX:ParallelGCThreads进行调节。
3、Parallel Scavenge垃圾回收器,多线程,复制算法,吞吐量可控
与ParNew垃圾回收器不同的时,Parallel Scavenge垃圾回收器追求的目标是"可控制的吞吐量",是一种吞吐量优先的垃圾回收器;
此外,自适应调节策略也是与ParNew的重要区别。

垃圾回收的两个关键指标:
1)停顿时间,也就是导致用户线程暂停的STW(stop the world)时间;
2)吞吐量,即用户代码运行时间占比,计算公式为 吞吐量 = 用户代码运行时间 / (用户代码运行时间 + 垃圾回收运行时间)。

Parallel Scavenge垃圾回收器提供了三个参数用于调控这两个指标:
1)-XX:MaxGCPauseMillis,最大停顿(STW)时间,单位毫秒。
注意这个值并不是越低越好,因为要满足过低的停顿时间,加快单次GC的速度,垃圾回收器工作过程中可能会降低堆大小,但这样又会导致GC更加频繁,整体来看反而降低了系统的吞吐量。对于吞吐量优先的垃圾回收,此参数需谨慎使用。
2)-XX:GCTimeRatio,用来调整垃圾回收时间占比,从而控制吞吐量。
GCTimeRatio = 用户代码运行时间 / 垃圾回收时间,默认值99,即1%的时间用来垃圾回收。
3)-XX:UseAdaptiveSizePolicy,是否启用自适应调节,默认开启。
此模式下,一些GC参数不需要手动设置,比如年轻代大小、Eden和Survivor区比例以及晋升老年代的年龄阈值等,而是交由虚拟机自适应得去进行调整,从而达成-XX:MaxGCPauseMillis或-XX:GCTimeRatio设定的目标。

老年代垃圾回收器
1、Serial Old垃圾回收器,单线程,标记整理算法
Serial垃圾回收器的老年代实现,是JVM运行在客户端模式下的默认老年代垃圾回收器。
2、Parallel Old垃圾回收器,多线程,标记整理算法
同样是吞吐量优先的垃圾回收器,与Parallel Scavenge垃圾回收器搭配使用,PS+PO也是JDK 1.8版本的默认垃圾回收器组合。
3、CMS(Concurrent Mark Sweep)垃圾回收器,多线程,标记清除算法
CMS算法为了追求最短的停顿时间,采用了在垃圾回收的部分环节与用户线程并发执行的方式,工作流程如下图:
在这里插入图片描述
CMS垃圾回收的详细阶段:
1、初始标记。
此阶段标记两类对象:GC Roots直接关联的老年代对象,以及被年轻代对象所引用的老年代对象(需要遍历年轻代对象)。该阶段时间很短,但会stop the world。
2、并发标记。
根据初始标记结果,沿着引用链进行追踪标记,最终标记出存活对象;此过程GC线程与用户线程并发执行。
3、并发预清理。
并发预清理、可中断的并发预清理这两个阶段和重新标记阶段所做的工作类似,都是为了解决并发标记阶段的漏标问题。由于重新标记阶段是STW的,为了减少STW的时间,CMS将重新标记的部分工作前置,放在并发预清理、可中断的并发预清理这两个阶段。
并发预清理的主要工作是扫描由于对象引用关系变更而产生的dirty card,标记上一阶段没有标记到的存活对象。该阶段默认开启,可通过参数设置关闭。
4、可中断的并发预清理。
该阶段的主要价值在于能够控制进入重新标记阶段的时机,同时期望在此阶段内发生一次MinorGC(只是期望),这样重新标记阶段扫描年轻代的工作量会小很多。
该阶段主要是循环的执行以下工作:
1)处理Survivor区中的对象,标记可达的老年代对象;
2)和上一阶段一样,处理老年代中的dirty card,标记存货对象。
该阶段的启动时机通过参数CMSScheduleRemarkEdenSizeThreshold控制,默认为2M,即eden区内存占用超过该值时触发。
循环的中断时机也可以通过参数控制(循环次数、执行时间、eden区内存使用率)。
5、重新标记。
由于并发标记和用户线程是同步进行的,可能会出现一些漏标的情况,也就是说存活对象被误识别为了垃圾。比如:
1)并发标记阶段确认的垃圾对象被年轻代对象引用,变成了非垃圾;
2)年轻代的存活对象晋升到了老年代;
3)新的大对象直接被分配到了老年代;
4)老年代中的对象引用关系发生了改变,比如存活对象引用了一个垃圾对象,使其变为非垃圾。
而重新标记的过程,会进行以下操作,对标记结果进行最终修正:
1)重新遍历年轻代对象,并沿引用链找到老年代存活对象;
2)重新扫描GC Roots对象,并沿引用链找到老年代存活对象;
3)老年代中的对象引用关系变更,会在并发标记阶段被记录下来(dirty card);重新标记阶段会对dirty card进行扫描,并沿引用链找到存活对象。
重新标记阶段会stop the world。
6、并发清除。对垃圾对象进行清除,与用户线程并发执行。
7、并发重置。重置数据结构和内部状态,为下次GC做准备。

关于card table、mod union table
card table
又称卡表,是JVM为了解决垃圾回收中的跨代引用问题而设计的一种数据结构。
卡表将堆内存划分为若干个大小相等的区间(默认为512字节),称为卡页(card page),每个card page中有若干个对象;而card table是一个字节数组,它的每个元素对应着一个card page。
当对象引用关系变更时,通过写屏障逻辑(可以理解为赋值后的一个切面操作)修改card table中的元素值,将其对应的card page标记为dirty,即dirty card。比如A.a = B变为A.a = C,说明A对象引用关系变更,A对象所在的Card Page被标记为dirty。
MinorGC中,除了扫描年轻代中GC Roots对象,还需要扫描那些被老年代对象引用着的年轻代对象,即跨代引用。有了卡表,就不用对整个老年代进行遍历,而是只需要扫描dirty card中的对象,就能找到跨代引用关系。
实际上,垃圾回收中使用的card table都是针对老年代而言,在年轻代中使用card table并没有什么意义,因为年轻代对象“朝生夕死”的特性,维护card table的工作量巨大,还不如扫描整个年轻代。

mod union table
card table记录了对象的引用关系变更,dirty card在MinorGC中将会被扫描;而CMS同样要关注老年代中对象的引用关系变更,扫描dirty card。但如果MinorGC和MajorGC共用card table,会有一个问题,就是在CMS的MajorGC过程中,并发地进行了一次或多次的MinorGC,MinorGC扫描dirty card时,如果发现里面没有老年代到年轻代的引用,就会将该card置为clean,这样MajorGC所需要扫描的dirty card就减少了,会产生漏标;所以MinorGC重置dirty card时,必须有一种机制能够消除这种影响,于是在CMS中新增了一个叫mod union table的数据结构。
mod union table是一个位向量,每一位对应一个Card Page。当MinorGC扫描card table中的dirty card时,会将mod union table中与dirty card对应的位给置位,这样即使card table中的card被置为clean了,mod union table也保留了这个脏卡记录,CMS寻找dirty card时,会同时扫描card table和mod union table,只要在任意table中标记为dirty,即为dirty card。
既然并发的MinorGC修改card table会影响MajorGC,那反过来,MajorGC在并发预清理阶段清理dirty card时,会不会影响到正在进行的MinorGC呢?实际上,CMS在预清理阶段清理掉的dirty card,会被标记为precleaned状态,这个状态对于MinorGC来讲相当于dirty,对于重新标记阶段来讲相当于clean。

三色标记法
CMS标记对象使用了三色标记算法,使用黑、白、灰三色代指对象的三种状态。
黑色:该对象已访问,且其引用的对象也已经全部访问;
灰色:该对象已访问,但其引用的对象并没有全部被访问;
白色:该对象未访问。

由于并发标记和用户线程是同步的,所以用户线程修改对象的引用关系,可能会对标记结果产生以下影响:
1、该回收的不回收。如下图:
在这里插入图片描述
对象C已经访问完成,被GC线程标记为黑色,但是用户线程随后切断了对象B到C的引用,导致C变为不可达状态;但此时C已经被标记黑色,GC线程认为不是垃圾,本次不回收,所以对象C只能等到下次GC时再回收,这种垃圾也称作"浮动垃圾"。
2、不该回收的回收了。如下图:
在这里插入图片描述
对象A已经被标记为黑色,但随后用户线程增加了A到D的引用,同时断掉了B到D的引用;此时由于B到D不再可达,同时A又不会再被关注,导致GC线程访问不到D,认为D是垃圾对象,而实际上A指向了D,D是有用的。
CMS垃圾回收器对于这种引用关系变更,使用了增量更新的策略,通过写屏障(写后屏障,可以理解为修改应用关系后的一种切面操作)将A到D的新增引用记录下来,然后将A更新为灰色,在重新标记阶段对A和D进行标记。

CMS垃圾回收器的一些缺点:
1、与用户线程竞争CPU资源,可能会影响系统性能;
2、标记清除算法所带来的内存碎片问题。过多的内存碎片可能会导致更加频繁的FullGC,可通过两个参数
-XX:+UseCMSCompactAtFullCollection和-XX:CMSFullGCsBeforeCompaction指定多少次FullGC后进行一次内存整理的工作,以减少内存碎片。
3、浮动垃圾与并发失败。在CMS和用户线程并行执行的过程中,用户线程也在不停地生成新的对象和垃圾,这些对象只能在下一次GC时被回收,属于"浮动垃圾",所以CMS工作时必须要留出足够的内存空间给用户线程使用。但是如果预留空间设置不合理,导致CMS垃圾回收的过程中,对象进入老年代发现剩余空间不足,就会出现"Comcurrent Mode Failure",此时虚拟机会临时启用Serial Old串行垃圾回收器来对老年代进行垃圾回收,会长时间stop the world。

G1垃圾回收器
G1(Garbage First)是JDK 9及以后版本的默认垃圾回收器,它的设计是为了取代CMS。
G1相对于CMS有着比较突出的优势:
1、可预测的停顿时间。允许用户设置最大停顿时间,并在尽可能满足该停顿时间目标的同时,保证高吞吐量。
2、整体上来看使用标记整理算法,很大程度上解决了内存碎片的问题。

G1的堆内存划分
G1将堆内存从物理上划分为若干个大小相等的区域(Region),每个区域在逻辑上可以代表年轻代或者老年代。也就是说,此时的堆内存
在逻辑上是分代的,但是在物理上是分区的,年轻代/老年代的物理内存不一定连续。
根据实际堆内存的大小,region大小通常在1~32M,整体region个数在2048左右。
在这里插入图片描述
G1的内存模型中新增了一个Humongous区(H区),用来存放体积大于等于region容量50%的巨型对象,如果一个H区无法容纳,G1会寻找连续的H区来存放这个对象。
大对象默认是放在Old区,但如果这些巨型对象的生命周期较短,放在老年代反而会对垃圾回收器产生负面影响;Humongous区就是为了解决这个问题。

G1垃圾回收的两种模式

1、Young GC(MinorGC)
Young GC回收整个年轻代,会stop the world。
同之前的年轻代垃圾回收器一样,当年轻代Eden区空间不足时,启动Young GC,存活对象将会被复制到Survivor区或者老年代。
G1的初始年轻代占比由参数-XX:G1NewSizePercent确定,默认5%;随着系统运行,会动态地分配更多年轻代region,年轻代占比的上限由参数-XX:G1MaxNewSizePercent确定,默认60%。
G1会通过调整年轻代region的个数来控制年轻代大小,从而使垃圾回收能够满足预设的停顿时间目标。
每次Young GC过后,G1都会基于历史Young GC的长期观察数据,对年轻代大小进行一次调整。

Remembered Set & Card Table
G1在Young GC标记存活对象的过程中,同样要考虑跨代引用问题,即哪些老年代对象引用了年轻代对象。
为了避免对整个老年代的扫描,G1采用了记忆集(Remembered Set,简称RSet)和卡表(Card Table)这两个结构。
card table将对空间从逻辑上划分为若干个大小相等的card,而card table是一个字节数组,每个元素都对应着一个card。
每个region都有一个RSet,用来记录其他region中的对象到自身对象的引用。RSet本质上是一个HashTable结构,key是region的起始地址,value是一个集合,元素为card table的索引。

RSet的更新
RSet的更新依赖card table的写后屏障逻辑来实现,当对象引用关系变更后,该对象所在的card被标记为dirty,为了减少性能开销,G1会将dirty card丢入dirty card queue中,而除了线程私有的dirty card queue,还有一个全局的队列集合DirtyCardQueueSet,满的dirty card queue会进入DirtyCardQueueSet。
实际更新RSet的操作由多个名叫ConcurrentG1RefineThread的线程并发执行;当DirtyCardQueueSet超过一定阈值后,ConcurrentG1RefineThread取出若干队列,遍历队列中的card,经过计算和筛选后,将相关信息更新到对应的RSet中。

2、Mixed GC
Mixed GC回收全部年轻代region和部分老年代region。
当老年代的空间分配达到整堆空间的比例阈值时,就会触发Mixed GC的并发标记周期;该阈值由参数-XX:InitiatingHeapOccupancyPercent确定,默认45%,即老年代空间占整堆的45%时触发。
Mixed GC总是开始于一次常规的Young GC,因为并发标记周期的初始标记阶段和Young GC共用一段停顿时间(初始标记需要扫描年轻代,而Young GC正好整理年轻代)。
G1会跟踪应用程序的行为和历史垃圾回收数据,建议一个停顿时间预测模型。在对老年代region的全局并发标记完成后,会结合各region的可回收空间及回收时间成本等因素,评估该region的回收价值,然后在尽量满足停顿时间目标的前提下,优先选择回收价值最大的那部分region进行回收,这也是Garbage First命名的由来。
Mixed GC整体上分为两个大的阶段,并发标记周期和空间回收(混合回收)。

并发标记周期
1、初始标记。
该阶段stop the world,借用Young GC的暂停时间,标记GC Roots直接关联的老年代对象,以及可能引用老年代对象的幸存者区域(根区域)。
2、根区域扫描。
该阶段与应用程序并发执行,扫描初始标记的Survivor区,标记被Survivor区对象所引用的老年代对象。此阶段必须在下次Young GC之前执行完,不然Survivor区会被破坏。
3、并发标记。
该阶段与应用程序并发执行,根据前面标记的存活对象进行引用链追踪,标记更多存活对象,同时计算region中的对象活性(存活对象比例)。
该阶段可以被Young GC中断,另外如果标记过程中发现某个region中没有存活对象,会立即对该region进行回收。
SATB
G1同样使用三色标记法来标记对象,在并发标记过程中,同样也会遇到引用关系变更带来的漏标问题:灰色对象到白色对象的直接或间接引用全部断开,同时黑色对象引用了该白色对象。
CMS采用增量更新策略(写后屏障,记录新增引用关系,将黑色对象变成灰色)解决上述问题,但是重新扫描灰色对象,会带来很多重复的扫描工作。相比之下,G1采用的是更快的SATB(Snapshot-At-The-Beginning),也叫原始快照策略。
SATB通过写前屏障逻辑,在旧的引用关系断开时,将引用关系记录在线程独占的satb mark queue中,满的satb mark queue会进入全局的队列集合SATBMarkQueueSet。并发标记阶段会定期检查SATBMarkQueueSet,当其容量达到阈值后,会处理其中的记录,将旧引用关系中被引用的白色对象标记为灰色,使其能被进一步扫描。
如果黑色对象直接引用了一个新分配的白色对象,不涉及旧引用的断开,这个白色对象是否会漏标呢?
实际上,G1在每个region中都设计了两个TAMS(top-at-mark-start)指针,用来控制并发标记阶段新对象的地址分配。该阶段新分配的对象都在[next TAMS,top]的指针区间内,这些对象被默认为存活(隐式标记),所以不会被遗漏。
4、最终/重新标记。
该阶段会stop the world,主要进行以下工作:
1)处理并发标记阶段未处理完的少量SATB记录,完成剩余存活对象的标记,同时计算对象活性。
2)进行引用处理。
5、清理阶段。
该阶段并非真正意义上的空间回收,即疏散存活对象;而是主要进行以下工作:
1)统计region中的存活对象及回收比例,并进行排序,为空间回收阶段做准备;同时识别出完全空闲的region(没有存活对象)。该过程STW。
2)整理Remembered Sets,该过程STW。
3)清理空闲region,并返回到空闲列表中,该过程与应用程序并发执行。

并发标记周期结束后,G1会判断当前可回收空间占整堆的比例是否超过允许的空间浪费占比阈值(该阈值由参数-XX:G1HeapWastePercent确定,默认5%),如果不超过,则不会进入空间回收阶段。

空间回收
该阶段回收全部年轻代region和部分老年代region,因为涉及到对象拷贝工作,所以需要stop the world。
G1将老年代region,按照之前计算的回收效率由高到低依次放入最终的collection set中,添加过程需要考虑两个因素:
1)整体回收时间不超过预设的停顿时间目标。
2)region中的存活对象比例低于阈值时,才进行回收。该阈值由参数-XX:G1MixedGCLiveThresholdPercent确定,默认65%。
老年代region的回收通常需要进行多次,每次回收一定比例的region,回收次数受到两个参数影响:
1)-XX:G1MixedGCCountTarget,指定空间回收的次数,默认为8。
2)-XX:G1HeapWastePercent,允许的堆空间浪费比例,默认为5%;如果发现可回收内存低于该值,则停止空间回收。

Full GC
和其他垃圾回收器一样,G1在垃圾回收过程中,如果剩余空间无法满足新对象的分配,也会触发Full GC(stop the world)。

巨型对象回收
一般来说,不可访问的巨型对象,只能在并发标记周期中清理阶段的暂停时间内回收,或者在Full GC期间回收。不过,对于基本类型数组(例如布尔数组、各种整数数组和浮点数数组)的巨型对象有一个特殊的规定,即在任何类型的GC暂停时间,G1都会机会主义地尝试回收掉这些不可访问的巨型对象。这个特殊行为默认开启,可使用参数-XX:G1EagerReclaimHumongousObjects禁掉。

后记

在分析具体的垃圾回收案例时,需要关注当前JDK的版本,因为复杂的垃圾回收器总是会在多个JDK的版本中进行持续优化,在实现上可能存在一些版本差异。

参考资料:
CMS:
https://www.jianshu.com/p/57db02feaca4
https://www.cs.purdue.edu/homes/hosking/ismm2000/papers/printezis.pdf
G1:
https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/G1GettingStarted/index.html
https://docs.oracle.com/javase/9/gctuning/garbage-first-garbage-collector.htm#JSGCT-GUID-ED3AB6D3-FD9B-4447-9EDF-983ED2F7A573
https://blog.csdn.net/m0_66491772/article/details/122643276

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值