JVM 垃圾回收

1、如何判断对象是否死亡(两种方法)

有两种方法判定对象死亡:引用计数法、可达性分析算法

  • 引用计数法:每当有一个地方引用它,计数器+1。当引用失效,计数器-1。任何计数器为 0 的对象都是不再使用的。缺点是无法解决循环引用问题。
public class ReferenceCountingGc {
    Object instance = null;
    public static void main(String[] args) {
        ReferenceCountingGc objA = new ReferenceCountingGc();
        ReferenceCountingGc objB = new ReferenceCountingGc();
        objA.instance = objB;
        objB.instance = objA;
        objA = null;
        objB = null;
    }
}
  • 可达性分析算法:以 "GC Roots"作为起点,向下搜索所走过的路径称为引用链。当一个对象到 "GC Roots"无任务引用链时,则证明此对象不可达,需要被回收。哪些对象可以作为 GC Roots 呢?虚拟机栈(栈帧中的局部变量表)中引用的对象;本地方法栈(Native 方法)中引用的对象;方法区中类静态属性引用的对象;方法区中常量引用的对象;所有被同步锁持有的对象;JNI(Java Native Interface)引用的对象。
2、简单的介绍一下强引用、软引用、弱引用、虚引用(虚引用与软引用和弱引用的区别、使用软引用能带来的好处)

引用的强弱程度,决定了对象被垃圾回收的时机和条件:

  • 强引用 (Strong Reference)
Object obj = new Object(); // 强引用

最常见的引用类型,GC 永远不会回收强引用对象,即使内存不足时,宁愿抛 OOM 也不会回收,当 obj = null 时,对象才可能被回收。

  • 软引用 (Soft Reference)
SoftReference<Object> soft = new SoftReference<>(new Object());

内存充足时不会被回收,内存不足时会被回收,常用于实现内存敏感的缓存,可以配合引用队列使用。

  • 弱引用 (Weak Reference)
WeakReference<Object> weak = new WeakReference<>(new Object());

比软引用更弱,只要发生 GC 就会被回收,常用于避免内存泄漏,WeakHashMap 就是基于弱引用实现。

  • 虚引用 (Phantom Reference)
ReferenceQueue<Object> queue = new ReferenceQueue<>();
PhantomReference<Object> phantom = new PhantomReference<>(new Object(), queue);

最弱的引用类型,随时可能被回收,必须和引用队列配合使用,主要用于跟踪对象被回收的状态。

3、如何判断一个常量是废弃常量

假如在字符串常量池中存在字符串 "abc",如果当前没有任何 String 对象引用该字符串常量的话,就说明常量 "abc" 就是废弃常量,如果这时发生内存回收的话而且有必要的话,"abc" 就会被系统清理出常量池了。

4、如何判断一个类是无用的类

判定方法区一个类是无用类,需要满足 3 个条件:

  • 该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。
  • 加载该类的 ClassLoader 已经被回收。
  • 该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
5、垃圾收集有哪些算法,各自的特点?
  • 标记-清除(mark-sweep):标记阶段,确定所有要回收的对象,并做标记;清除阶段,将标记阶段确定不可用的对象清除掉。缺点:标记和清除阶段效率都不高;会产生大量碎片导致频繁的回收。
  • 复制(copy):内存被分为大小相同的两块,每次用1 块,当开始垃圾回收,把存活的对象复制到另一块,然后这块内容整个清理。缺点:需要浪费额外的内存作为复制区;对于存活率较高,复制算法效率会降低。
  • 标记-整理(mark-compact):不是把存活的对象复制到另一块内存,而是把存活对象往内存一段移动,然后直接回收边界以外的内存。缺点:移动过程效率较低。
6、HotSpot 为什么要分为新生代和老年代
  • 对象生命周期差异,大多数对象生命周期短(如临时计算对象),仅需一次Minor GC即可回收;少数对象存活时间长(如全局配置对象),需多次Minor GC后晋升至老年代。分代策略通过区分对象生命周期,优化垃圾回收效率。
  • 垃圾回收算法适配性,新生代:采用复制算法,利用短命对象占比高的特性,将存活对象复制到Survivor区,清空Eden和另一Survivor区,避免内存碎片,提升回收速度。老年代:使用标记-清除或标记-整理算法,应对存活率高、内存占用大的对象,减少频繁Full GC的开销。
  • 减少GC停顿时间,新生代GC(Minor GC)频率高但耗时短,老年代GC(Major/Full GC)频率低但耗时长。分代后,短命对象回收不会频繁触发老年代GC,整体系统吞吐量更高。
  • 内存管理灵活性,不同代可独立配置GC参数(如Survivor区大小、晋升阈值),适配不同业务场景。例如,通过调整对象晋升年龄(默认15次Minor GC后晋升)控制老年代压力。
    总结:分代策略通过对象生命周期分类、算法优化和资源隔离,实现高效GC与低停顿,是JVM性能优化的核心设计之一。
7、常见的垃圾回收器有哪些?
image.png

如上图,重点关注 CMS+parNewGC组合、Parallel Scavenge+Parallel Old组合、G1GC

  1. ParNew 收集器
  • 定位:新生代收集器,多线程并行回收,与 CMS 搭配使用。
  • 回收过程:
    Minor GC(触发条件:Eden 区满),STW:暂停所有用户线程。
    复制算法:将 Eden 区和其中一个 Survivor 区的存活对象复制到另一个空闲 Survivor 区,清空原区域。
    对象晋升:若对象年龄超过阈值(默认 15 次 Minor GC),或 Survivor 区空间不足,则晋升到老年代。
  • 特点:并行多线程回收,吞吐量高,但需短暂 STW。与 CMS 搭配时,需注意内存碎片问题。
  1. CMS(Concurrent Mark Sweep)收集器
  • 定位:老年代收集器,追求低停顿,已逐渐被 G1 取代。
  • 回收过程(4 阶段):
    初始标记(Initial Mark),STW:标记 GC Roots 直接关联的对象,耗时极短。
    并发标记(Concurrent Marking),并发执行:从 GC Roots 遍历整个对象图,标记所有存活对象。
    重新标记(Remark),STW:修正并发标记期间因用户线程运行产生的对象变动(如新增引用)。
    并发清除(Concurrent Sweep),并发执行:清理标记为垃圾的对象,释放内存(标记-清除算法)。
  • 关键问题:
    浮动垃圾:并发阶段产生的新垃圾需下次 GC 处理。
    碎片化:标记-清除导致内存碎片,可能触发 Full GC。
    并发失败:内存不足时切换为 Serial Old 收集器。


    image.png
  1. G1(Garbage-First)收集器
  • 定位:全堆收集器,兼顾低延迟与高吞吐量,JDK 9+ 默认。
  • 回收过程(4 阶段):
    初始标记(Initial Mark),STW:标记 GC Roots 直接关联的对象,并同步记录 TAMS(Top of the Mark Stack)指针。
    并发标记(Concurrent Marking),并发执行:遍历堆内所有对象,标记存活对象,记录 SATB(Snapshot-At-The-Beginning)快照。
    最终标记(Final Remark),STW:修正并发阶段因用户线程修改引用关系导致的 SATB 快照差异。
    筛选回收(Mixed Collection),STW:根据回收收益(垃圾量/回收时间)选择多个 Region 组成回收集,将存活对象复制到新 Region,清理旧 Region(标记-整理算法)。
  • 核心机制:
    Region 划分:堆内存划分为多个等大小的独立区域,动态扮演新生代或老年代。
    混合回收:触发条件为老年代占用率超过阈值(默认 45%),逐步回收高垃圾率的 Region。
    可预测停顿:通过参数 -XX:MaxGCPauseMillis 控制最大停顿时间。
  • 大对象处理:Humongous 区域:超过 Region 50% 的对象存入 Humongous 区,回收时与其他 Region 同步处理


    image.png
8、如何解决GC 三色标记法漏标问题?

三色标记法含义:

  • 白色(White):未被扫描的对象,默认初始状态,若最终仍为白色则判定为垃圾。
  • 灰色(Gray):已扫描但存在未处理的引用,需进一步遍历其子对象。
  • 黑色(Black):已扫描且所有子对象均处理完毕,确认存活。

CMS:增量更新(Incremental Update)

  • 原理:当黑色对象新增对白色对象的引用时,通过写屏障将黑色对象重新标记为灰色,触发重新标记阶段(STW)时,以该灰色对象为根重新扫描其引用链,确保白色对象被标记为存活。
  • 关键点:关注引用的增加(A→C)。需重新扫描黑色对象的完整引用链,导致 STW 时间较长。
  • 缺点:效率较低(需遍历新增引用的完整链路)。

G1:SATB(Snapshot-At-The-Beginning)

  • 原理:并发标记开始时记录对象图的原始快照。当灰色对象断开对白色对象的引用时,通过写屏障将旧引用推入 SATB 队列,在最终标记阶段以白色对象为根重新扫描,确保其存活。
  • 关键点:关注引用的删除(B→C 的引用消失)。仅处理并发阶段删除的引用,STW 时间更短。
  • 缺点:可能产生浮动垃圾(白色对象本应被回收但存活到下次 GC)。


    image.png
9、如何解决GC跨代引用问题?
  1. 跨代引用问题的核心
  • 场景:YGC 时,新生代对象可能被老年代对象引用,需遍历老年代判断存活性,但老年代引用关系复杂,全量扫描耗时高。
  • 关键矛盾:跨代引用极少,但频繁 YGC 时全局扫描老年代效率低下。
  1. CMS 解决方案
  • 卡表(Card Table):
    作用:记录老年代内存块(卡页,512 字节)是否包含对新生代的引用。
    实现:通过写屏障(内存屏障)在对象赋值时标记卡页为“脏卡”,YGC 时仅扫描脏卡对应的内存区域。
  • 增量更新(Incremental Update):
    原理:当黑色对象新增对白色对象的引用时,将黑色对象标记为灰色,最终标记阶段重新扫描其引用链,避免漏标。
    缺点:需遍历黑色对象的完整引用链,STW 时间较长。
  1. G1 解决方案
  • 卡表 + RSet(Remembered Set):
    卡表:与 CMS 类似,记录跨代引用的内存块。
    RSet:每个 Region 维护一个哈希表,记录其他 Region 对本 Region 的引用,YGC 时仅扫描相关 Region。
  • SATB(Snapshot-At-The-Beginning):
    原理:并发标记开始时记录对象图快照。若灰色对象断开对白色对象的引用,通过 SATB 队列在最终标记阶段重新扫描白色对象,避免漏标。
    浮动垃圾:白色对象可能被误判为存活(本应回收),但减少 STW 时间。
image.png
8、垃圾回收器的常见参数有哪些?
  • Java栈:
-Xss #设置线程最大栈空间
  • Java 堆:
-Xms #设置堆空间初始大小(新生代+老年代)
-Xmx #设置堆空间最大大小(新生代+老年代)
-Xmn #新生代大小
-XX:NewSize=256M  #新生代最小 256m内存
-XX:MaxNewSize=1024M #新生代最大 1024m内存
-XX:NewRatio=<int> #设置老年代与新生代内存的比值
-XX:SurvivorRatio=<int> #设置Eden和 S0和 S1大小比例。设置成 8 代表Eden:S0:S1 = 8:1:1
-XX:MetaspaceSize=N #设置 Metaspace 的初始大小,触及MetaspaceSize,触发Full GC
-XX:MaxMetaspaceSize=N #设置 Metaspace 的最大大小,默认为-1,当没有本地内存可用,直接 OOM
-XX:MaxTenuringThreshold #设置默认的晋升年龄
  • GC 相关
-XX:+UseSerialGC #串行垃圾收集器
-XX:+UseParallelGC #并行垃圾收集器
-XX:+UseConcMarkSweepGC #CMS 垃圾收集器
-XX:+UseG1GC #G1 垃圾收集器
  • GC 日志相关
# 必选
# 打印基本 GC 信息
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
# 打印对象分布
-XX:+PrintTenuringDistribution
# 打印堆数据
-XX:+PrintHeapAtGC
# 打印Reference处理信息
# 强引用/弱引用/软引用/虚引用/finalize 相关的方法
-XX:+PrintReferenceGC
# 打印STW时间
-XX:+PrintGCApplicationStoppedTime

# 可选
# 打印safepoint信息,进入 STW 阶段之前,需要要找到一个合适的 safepoint
-XX:+PrintSafepointStatistics
-XX:PrintSafepointStatisticsCount=1

# GC日志输出的文件路径
-Xloggc:/path/to/gc-%t.log
# 开启日志文件分割
-XX:+UseGCLogFileRotation
# 最多分割几个文件,超过之后从头文件开始写
-XX:NumberOfGCLogFiles=14
# 每个文件上限大小,超过就触发分割
-XX:GCLogFileSize=50M
  • 处理 OOM
-XX:+HeapDumpOnOutOfMemoryError #示 JVM 在遇到 OutOfMemoryError 错误时将 heap 转储到物理文件中 
-XX:HeapDumpPath=./java_pid<pid>.hprof #表示要写入文件的路径; 可以给出任何文件名; 但是,如果 JVM 在名称中找到一个 <pid> 标记,则当前进程的进程 id 将附加到文件名中,并使用.hprof格式
-XX:OnOutOfMemoryError="< cmd args >;< cmd args >" #用于发出紧急命令,以便在内存不足的情况下执行; 应该在 cmd args 空间中使用适当的命令。例如,如果我们想在内存不足时重启服务器,我们可以设置参数: -XX:OnOutOfMemoryError="shutdown -r"
-XX:+UseGCOverheadLimit #它限制在抛出 OutOfMemory 错误之前在 GC 中花费的 VM 时间的比例
  • 配置示例G1
JVM_EXT_ARGS
-Dspring.profiles.active=prod -Dorg.jboss.logging.provider=slf4j

JVM_GC
-XX:-DisableExplicitGC -XX:+UseG1GC -XX:MaxGCPauseMillis=300 -XX:G1ReservePercent=15 -XX:+PrintFlagsFinal -XX:+PrintFlagsFinal -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -XX:+PrintGCApplicationStoppedTime -XX:+PrintAdaptiveSizePolicy -XX:+PrintSafepointStatistics -XX:PrintSafepointStatisticsCount=1 -XX:+PrintReferenceGC -verbose:gc -Xloggc:logs/loggc_%p.log

JVM_HEAP
-XX:+AggressiveOpts -Xmx4096m -Xms4096m -Xss512k -XX:MaxDirectMemorySize=512M -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=512M -XX:AutoBoxCacheMax=20000 -XX:+UseStringDeduplication -XX:InitiatingHeapOccupancyPercent=60 -XX:SoftRefLRUPolicyMSPerMB=0 -XX:-OmitStackTraceInFastThrow -XX:+AlwaysPreTouch -XX:-UseLargePages -XX:-UseBiasedLocking -XX:+HeapDumpOnOutOfMemoryError -XX:+ParallelRefProcEnabled

TEST_URL
http://localhost:8080/monitor/alive


/usr/local/java8/bin/java -server -Dfile.encoding=UTF-8 -Dsun.jnu.encoding=UTF-8 -Djava.io.tmpdir=/tmp -Djava.net.preferIPv6Addresses=false -Dspring.profiles.active=prod -Dorg.jboss.logging.provider=slf4j 
-XX:ActiveProcessorCount=4 -XX:-UseContainerSupport -XX:+AggressiveOpts -Xmx4096m -Xms4096m -Xss512k -XX:MaxDirectMemorySize=512M -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=512M -XX:AutoBoxCacheMax=20000 -XX:+UseStringDeduplication -XX:InitiatingHeapOccupancyPercent=60 -XX:SoftRefLRUPolicyMSPerMB=0 -XX:-OmitStackTraceInFastThrow -XX:+AlwaysPreTouch -XX:-UseLargePages -XX:-UseBiasedLocking -XX:+HeapDumpOnOutOfMemoryError -XX:+ParallelRefProcEnabled -XX:-DisableExplicitGC -XX:+UseG1GC -XX:MaxGCPauseMillis=300 -XX:G1ReservePercent=15 -XX:+PrintFlagsFinal -XX:+PrintFlagsFinal -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -XX:+PrintGCApplicationStoppedTime -XX:+PrintAdaptiveSizePolicy -XX:+PrintSafepointStatistics -XX:PrintSafepointStatisticsCount=1 -XX:+PrintReferenceGC 
-verbose:gc -Xloggc:logs/loggc_%p.log -XX:ErrorFile=/opt/logs/{appkey}/vmerr.log -XX:HeapDumpPath=/opt/logs/{appkey}/HeapDump -jar ./{appkey}.jar

Non-default VM flags: -XX:ActiveProcessorCount=4 -XX:+AggressiveOpts -XX:+AlwaysPreTouch -XX:AutoBoxCacheMax=20000 -XX:CICompilerCount=3 -XX:CompressedClassSpaceSize=528482304 -XX:ConcGCThreads=1 -XX:-DisableExplicitGC -XX:ErrorFile=null -XX:G1HeapRegionSize=2097152 -XX:G1ReservePercent=15 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=null -XX:InitialHeapSize=4294967296 -XX:InitiatingHeapOccupancyPercent=60 -XX:MarkStackSize=4194304 -XX:MaxDirectMemorySize=536870912 -XX:MaxGCPauseMillis=300 -XX:MaxHeapSize=4294967296 -XX:MaxMetaspaceSize=536870912 -XX:MaxNewSize=2575302656 -XX:MetaspaceSize=268435456 -XX:MinHeapDeltaBytes=2097152 -XX:-OmitStackTraceInFastThrow -XX:+ParallelRefProcEnabled -XX:+PrintAdaptiveSizePolicy -XX:+PrintFlagsFinal -XX:+PrintGC -XX:+PrintGCApplicationStoppedTime -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintHeapAtGC -XX:+PrintReferenceGC -XX:+PrintSafepointStatistics -XX:PrintSafepointStatisticsCount=1 -XX:+PrintTenuringDistribution -XX:SoftRefLRUPolicyMSPerMB=0 -XX:ThreadStackSize=512 -XX:-UseBiasedLocking -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseContainerSupport -XX:+UseFastUnorderedTimeStamps -XX:+UseG1GC -XX:-UseLargePages -XX:+UseStringDeduplication 
  • 配置示例CMS
JVM_EXT_ARGS
-Dspring.profiles.active=prod -Dorg.jboss.logging.provider=slf4j

JVM_GC
-XX:+PrintFlagsFinal -XX:+PrintGCApplicationStoppedTime -XX:+PrintGCDetails -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -Xloggc:logs/gc.log -XX:+UseConcMarkSweepGC

JVM_HEAP
-Xmx4608m -Xms4608m -Xmn2560m -XX:MetaspaceSize=512m -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError

TEST_URL
http://localhost:8080/monitor/alive

/usr/local/java8/bin/java -server -Dfile.encoding=UTF-8 -Dsun.jnu.encoding=UTF-8 -Djava.io.tmpdir=/tmp -Djava.net.preferIPv6Addresses=false -Dspring.profiles.active=prod -Dorg.jboss.logging.provider=slf4j 
-XX:ActiveProcessorCount=4 -XX:-UseContainerSupport -Xmx4608m -Xms4608m -Xmn2560m -XX:MetaspaceSize=512m -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintFlagsFinal -XX:+PrintGCApplicationStoppedTime -XX:+PrintGCDetails -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -Xloggc:logs/gc.log -XX:+UseConcMarkSweepGC 
-XX:ErrorFile=/opt/logs/{appkey}/vmerr.log -XX:HeapDumpPath=/opt/logs/{appkey}/HeapDump -jar ./{appkey}.jar

Non-default VM flags: -XX:ActiveProcessorCount=4 -XX:CICompilerCount=3 -XX:CompressedClassSpaceSize=528482304 -XX:ErrorFile=null -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=null -XX:InitialHeapSize=4831838208 -XX:MaxHeapSize=4831838208 -XX:MaxMetaspaceSize=536870912 -XX:MaxNewSize=2684354560 -XX:MaxTenuringThreshold=6 -XX:MetaspaceSize=536870912 -XX:MinHeapDeltaBytes=196608 -XX:NewSize=2684354560 -XX:OldPLABSize=16 -XX:OldSize=2147483648 -XX:+PrintFlagsFinal -XX:+PrintGC -XX:+PrintGCApplicationStoppedTime -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseConcMarkSweepGC -XX:-UseContainerSupport -XX:+UseFastUnorderedTimeStamps -XX:+UseParNewGC 
image.png

image.png
9、Minor Gc 和 Full GC 有什么不同呢
image.png

CMS 收集器中的表现

  • Minor GC
    触发条件:Eden 区满时触发,使用复制算法将存活对象移动到 Survivor 区。
    关键机制:对象年龄达到阈值(默认 15 次 Minor GC 后)晋升到老年代。动态年龄判定:若 Survivor 区剩余空间不足以容纳存活对象,则直接晋升。
  • Full GC
    触发场景:Promotion Failure:Survivor 区溢出且老年代空间不足。CMS Concurrency Mode Failure:并发标记阶段用户线程产生过多垃圾,老年代预留空间耗尽。
    回收策略:停止所有用户线程,对整个堆进行标记-清除或标记-整理

G1 收集器中的表现

  • Mixed GC(类比 Full GC 的部分行为)
    触发条件:老年代占用超过 45% 时触发,回收部分老年代 Region。
    特点:分阶段回收:优先回收垃圾比例高的 Region(Garbage First)。停顿可控:通过 MaxGCPauseMillis 参数限制单次停顿时间。
  • Full GC(G1 的特殊场景)
    触发场景:元空间(Metaspace)不足。混合回收无法满足停顿目标时退化为 Full GC。
    回收策略:对整个堆进行全量标记-整理,耗时显著增加。
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 一、垃圾回收 1、如何判断对象是否存活 1、引用计数法 在对象中添加一个引用计数器,当有地方引用这个对象的时候,引...
    奈何缘浅wyj阅读 1,917评论 0 1
  • 前言 垃圾:简单说就是内存中已经不在被使用到的内存空间就是垃圾。 垃圾回收(Garbage Collection,...
    小波同学阅读 4,866评论 1 4
  • 识别垃圾算法 引用计数法 可达性算法 清除垃圾算法 标记清除算法 复制算法 标记整理算法 分代回收 一、引用计数法...
    我可能是个假开发阅读 514评论 0 0
  • 写在前面 简单的介绍一下JVM(Java Virtual Machine)吧,它也叫Java虚拟机。虽然它叫虚拟机...
    SH的全栈笔记阅读 1,417评论 0 0
  • 什么是垃圾 引用计数法 给对象中添加一个引用计数器,每当有一个地方引用它,计数器就加 1;当引用失效,计数器就减 ...
    清灬风阅读 21评论 0 1