news 2026/4/16 17:29:35

JVM垃圾回收算法与收集器面试题详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JVM垃圾回收算法与收集器面试题详解

一、垃圾回收算法基础

1. 请详细说明主流的垃圾回收算法及其优缺点

问题分析角度:

  • 考察对GC算法理论基础的掌握
  • 考察算法适用场景的判断能力
  • 考察算法演进过程的理解

1.1 标记-清除算法(Mark-Sweep)

算法原理:

  1. 标记阶段:标记所有需要回收的对象
  2. 清除阶段:统一回收所有被标记的对象
// 伪代码示例voidmarkSweep(){// 标记阶段for(Objectroot:gcRoots){mark(root);}// 清除阶段for(Objectobj:heap){if(!isMarked(obj)){free(obj);}}}voidmark(Objectobj){if(obj==null||isMarked(obj))return;setMarked(obj);for(Objectref:obj.getReferences()){mark(ref);}}

优点:

  • 实现简单
  • 不需要移动对象

缺点:

  1. 效率问题:标记和清除效率都不高,需要扫描整个堆
  2. 空间问题:产生大量内存碎片,可能导致大对象无法分配
回收前: [A][B][C][D][E][F] 回收后: [A][ ][C][ ][E][ ] // 产生碎片

应用场景:CMS收集器的老年代回收


1.2 标记-复制算法(Mark-Copy)

算法原理:

  1. 将内存分为大小相等的两块
  2. 使用其中一块,满了就将存活对象复制到另一块
  3. 清空已使用的那块内存
// 伪代码示例voidcopyingGC(){Object[]newSpace=allocateNewSpace();intnewIndex=0;// 复制所有存活对象for(Objectroot:gcRoots){newIndex=copy(root,newSpace,newIndex);}// 交换空间swapSpaces();}intcopy(Objectobj,Object[]newSpace,intindex){if(obj==null||obj.forwardingPointer!=null){returnindex;}// 复制对象到新空间newSpace[index]=obj.clone();obj.forwardingPointer=newSpace[index];// 更新引用for(Objectref:obj.getReferences()){index=copy(ref,newSpace,index+1);}returnindex;}

优点:

  • 实现简单
  • 运行高效
  • 没有内存碎片

缺点:

  • 内存利用率低(只能使用50%)
  • 存活对象多时效率降低

优化方案 - Appel式回收:

新生代划分: Eden(80%) + Survivor0(10%) + Survivor1(10%) [Eden 80%][S0 10%][S1 10%] 工作流程: 1. 对象优先在Eden分配 2. Eden满时,触发Minor GC 3. 存活对象复制到空闲的Survivor 4. 清空Eden和使用过的Survivor

实际效果:

// 新生代对象存活率通常很低(研究表明<10%)// 使用8:1:1的比例,实际可用内存达到90%-XX:SurvivorRatio=8// Eden/Survivor=8-XX:+UseAdaptiveSizePolicy// 动态调整比例

应用场景:新生代回收(Serial、ParNew、Parallel Scavenge、G1的Young GC)


1.3 标记-整理算法(Mark-Compact)

算法原理:

  1. 标记阶段:标记所有存活对象
  2. 整理阶段:将所有存活对象移动到一端,清理边界外的内存
// 伪代码示例voidmarkCompact(){// 标记阶段for(Objectroot:gcRoots){mark(root);}// 计算新地址intnewAddress=heapStart;for(Objectobj:heap){if(isMarked(obj)){obj.forwardingAddress=newAddress;newAddress+=obj.size();}}// 更新引用updateReferences();// 移动对象for(Objectobj:heap){if(isMarked(obj)){move(obj,obj.forwardingAddress);}}}

优点:

  • 没有内存碎片
  • 不浪费空间
  • 适合老年代(对象存活率高)

缺点:

  • 移动对象成本高
  • 需要暂停用户线程(Stop The World)

性能对比:

场景: 100MB堆,存活率90% 标记-复制: 需要复制90MB对象 标记-整理: 需要移动对象并更新引用,但不浪费空间 老年代选择标记-整理是因为: 1. 存活率高,复制成本大 2. 不能浪费50%的空间

应用场景:老年代回收(Serial Old、Parallel Old、G1的Mixed GC)


1.4 分代收集理论

核心假说:

  1. 弱分代假说:绝大多数对象都是朝生夕灭
  2. 强分代假说:熬过多次GC的对象越难消亡
  3. 跨代引用假说:跨代引用相对同代引用占极少数

分代设计:

堆内存结构: +----------------------------------+ | 新生代(Young Gen) | | Eden | Survivor0 | Survivor1| | (8) | (1) | (1) | +----------------------------------+ | 老年代(Old Gen) | | | +----------------------------------+ 对象晋升规则: 1. 大对象直接进入老年代 -XX:PretenureSizeThreshold=3m 2. 长期存活对象进入老年代 -XX:MaxTenuringThreshold=15 3. 动态年龄判定 Survivor中相同年龄对象总大小 > Survivor空间一半 则年龄>=该年龄的对象进入老年代 4. 空间分配担保 Minor GC前检查老年代最大可用连续空间 是否大于新生代所有对象总空间

实战案例:

publicclassGenerationExample{// 大对象直接进入老年代byte[]bigObject=newbyte[4*1024*1024];// 4MBpublicstaticvoidmain(String[]args){// 短命对象-在新生代回收for(inti=0;i<1000000;i++){Stringtemp=newString("temp"+i);}// 长命对象-晋升到老年代List<String>cache=newArrayList<>();for(inti=0;i<1000;i++){cache.add(newString("cache"+i));}// 触发多次Minor GC后,cache对象会晋升到老年代for(inti=0;i<10;i++){System.gc();}}}

二、垃圾收集器详解

2. 请详细对比各种垃圾收集器的特点和适用场景

问题分析角度:

  • 考察对各种收集器的深入理解
  • 考察收集器选择的实战经验
  • 考察新一代收集器的认知

2.1 Serial收集器(串行收集器)

特点:

  • 单线程收集
  • 新生代使用标记-复制算法
  • 老年代使用标记-整理算法
  • 收集时必须Stop The World
工作流程: 用户线程: ||||----[STW]----|||| Serial GC: [GC]

参数配置:

-XX:+UseSerialGC# 启用Serial收集器-XX:+UseSerialGC -XX:+UseSerialOldGC# 新生代+老年代都使用Serial

优点:

  • 简单高效
  • 单线程环境下停顿时间最短
  • 内存占用小

缺点:

  • 多核CPU下性能浪费
  • 停顿时间较长

适用场景:

  • Client模式下的默认收集器
  • 单核CPU或内存受限环境
  • 桌面应用程序

2.2 ParNew收集器(并行收集器)

特点:

  • Serial的多线程版本
  • 新生代并行,老年代串行
  • 可与CMS配合使用
工作流程: 用户线程: ||||----[STW]----|||| ParNew GC: [T1][T2][T3]

参数配置:

-XX:+UseParNewGC# 启用ParNew-XX:ParallelGCThreads=4# 设置并行线程数(默认=CPU核数)

性能对比:

// 测试场景: 4核CPU,2GB堆// Serial: 停顿200ms// ParNew: 停顿80ms (提升60%)// 但在单核环境下,ParNew可能慢于Serial// 因为线程切换开销

适用场景:

  • 多核CPU环境
  • 配合CMS使用(JDK9前唯一选择)

2.3 Parallel Scavenge收集器(吞吐量优先)

特点:

  • 关注吞吐量而非停顿时间
  • 提供自适应调节策略
  • 新生代收集器

核心概念:

吞吐量 = 运行用户代码时间 / (运行用户代码时间 + GC时间) 例如: 程序运行100分钟,GC 1分钟 吞吐量 = 99 / 100 = 99%

参数配置:

-XX:+UseParallelGC# 启用Parallel Scavenge-XX:MaxGCPauseMillis=100# 最大停顿时间100ms-XX:GCTimeRatio=99# 吞吐量目标99%-XX:+UseAdaptiveSizePolicy# 自适应调节(默认开启)# 自适应调节会自动调整:# -Xmn(新生代大小)# -XX:SurvivorRatio(Eden/Survivor比例)# -XX:PretenureSizeThreshold(大对象阈值)

吞吐量 vs 停顿时间:

高吞吐量场景: - 后台计算任务 - 批处理作业 - 科学计算 低停顿时间场景: - Web应用 - 交互式应用 - SLA要求高的服务

实战案例:

// 场景: 大数据批处理// 需求: 最大化CPU利用率,可以接受较长的停顿java-Xmx10g-Xms10g\-XX:+UseParallelGC\-XX:GCTimeRatio=99\-XX:+UseAdaptiveSizePolicy\-jar data-processor.jar// 结果: 吞吐量从95%提升到99%

适用场景:

  • 后台计算密集型应用
  • 批处理任务
  • 对停顿时间不敏感的场景

2.4 CMS收集器(Concurrent Mark Sweep)

设计目标:获取最短停顿时间

工作流程(四个阶段):

1. 初始标记(Initial Mark) - STW ||||----[STW]----|||| 标记GC Roots直接关联的对象(速度很快) 2. 并发标记(Concurrent Mark) 用户线程: |||||||||||||||| CMS线程: [标记整个引用链] 与用户线程并发执行 3. 重新标记(Remark) - STW ||||----[STW]----|||| 修正并发标记期间变动的对象(停顿时间较长) 4. 并发清除(Concurrent Sweep) 用户线程: |||||||||||||||| CMS线程: [清除未标记对象] 与用户线程并发执行

参数配置:

-XX:+UseConcMarkSweepGC# 启用CMS-XX:CMSInitiatingOccupancyFraction=75# 老年代使用75%触发CMS-XX:+UseCMSCompactAtFullCollection# Full GC后进行碎片整理-XX:CMSFullGCsBeforeCompaction=0# 多少次Full GC后整理-XX:+CMSParallelRemarkEnabled# 并行重新标记-XX:+CMSScavengeBeforeRemark# Remark前先做Minor GC

优点:

  • 并发收集,停顿时间短
  • 适合对响应时间敏感的应用

缺点:

  1. CPU资源敏感
// CMS默认启动线程数 = (CPU核数 + 3) / 4// 4核CPU: (4+3)/4 = 1个线程,占用25% CPU// 2核CPU: (2+3)/4 = 1个线程,占用50% CPU// 解决方案:-XX:ParallelCMSThreads=2// 手动设置CMS线程数
  1. 浮动垃圾(Floating Garbage)
// 并发清除阶段产生的新垃圾无法回收// 需要预留空间给用户线程// 如果预留空间不足,触发"Concurrent Mode Failure"// 启用后备方案Serial Old(Full GC),停顿时间很长// 优化:-XX:CMSInitiatingOccupancyFraction=70// 降低触发阈值
  1. 内存碎片
// 使用标记-清除算法,产生大量碎片// 可能导致大对象无法分配,触发Full GC// 解决方案:-XX:+UseCMSCompactAtFullCollection// Full GC时整理碎片-XX:CMSFullGCsBeforeCompaction=5// 5次Full GC后整理

实战案例:

# 电商网站配置(8核16G服务器)JAVA_OPTS=" -Xms8g -Xmx8g -Xmn3g -XX:+UseConcMarkSweepGC -XX:+UseCMSInitiatingOccupancyFraction -XX:CMSInitiatingOccupancyFraction=70 -XX:+CMSParallelRemarkEnabled -XX:+CMSScavengeBeforeRemark -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=5 -XX:+PrintGCDetails -XX:+PrintGCDateStamps "# 效果:# GC停顿时间: 50-100ms# 吞吐量: 95%# Full GC频率: 每天1-2次

适用场景:

  • 互联网应用
  • Web服务器
  • 对响应时间敏感的系统

2.5 G1收集器(Garbage First)

设计理念:

  • 面向服务端应用
  • 兼顾吞吐量和停顿时间
  • 可预测的停顿时间模型

内存布局革新:

传统分代: +------------------+------------------+ | 新生代(连续) | 老年代(连续) | +------------------+------------------+ G1分代: +---+---+---+---+---+---+---+---+ | E | E | S | O | O | E | H | O | // Region混合布局 +---+---+---+---+---+---+---+---+ E=Eden, S=Survivor, O=Old, H=Humongous 每个Region: 1MB-32MB(必须是2的幂) Humongous: 存储大对象(>= Region的50%)

核心概念 - Remembered Set(记忆集):

// 问题: 如何避免扫描整个堆来确定对象存活?// 解决: 每个Region维护一个RSet,记录外部指向本Region的引用RegionARSet:-RegionB的对象引用A中的对象-RegionD的对象引用A中的对象// Minor GC时只需扫描:// 1. GC Roots// 2. RSet中记录的引用

工作流程:

1. 初始标记(Initial Mark) - STW 附着在Minor GC中执行,标记GC Roots 2. 并发标记(Concurrent Mark) 从GC Roots开始对堆中对象进行可达性分析 与用户线程并发 3. 最终标记(Final Mark) - STW 处理并发标记阶段遗留的SATB记录 4. 筛选回收(Live Data Counting and Evacuation) - STW 对各Region回收价值排序 根据期望停顿时间制定回收计划 复制存活对象到空Region

参数配置:

-XX:+UseG1GC# 启用G1-XX:MaxGCPauseMillis=200# 期望最大停顿时间200ms-XX:G1HeapRegionSize=16m# Region大小16MB-XX:InitiatingHeapOccupancyPercent=45# 堆占用45%触发并发标记-XX:G1NewSizePercent=5# 新生代最小占比5%-XX:G1MaxNewSizePercent=60# 新生代最大占比60%-XX:G1ReservePercent=10# 保留空闲Region 10%-XX:ConcGCThreads=4# 并发GC线程数-XX:ParallelGCThreads=8# 并行GC线程数

Mixed GC详解:

// 当老年代占用达到阈值,触发Mixed GC// Mixed GC会回收部分老年代Region// G1根据价值优先选择回收的Region:// 价值 = 回收收益 / 回收时间回收收益=Region中垃圾对象占用的空间 回收时间=复制存活对象所需时间// 示例:RegionA:90%垃圾,需要10msRegionB:50%垃圾,需要20msRegionA价值=90%/10ms=9RegionB价值=50%/20ms=2.5// 优先回收Region A

实战调优案例:

# 场景: 4GB堆,要求停顿时间<100ms# 初始配置-Xms4g -Xmx4g -XX:+UseG1GC -XX:MaxGCPauseMillis=100# 问题: Young GC频繁,停顿时间超标# 分析GC日志:[GC pause(G1 Evacuation Pause)(young),0.15secs]# 新生代太小,频繁触发GC# 优化:-XX:G1NewSizePercent=30# 增加新生代最小占比-XX:G1MaxNewSizePercent=50# 限制新生代最大占比-XX:MaxGCPauseMillis=80# 降低目标停顿时间# 结果:# Young GC频率降低50%# 平均停顿时间: 70ms# 吞吐量提升: 92% -> 96%

对比CMS:

优势: 1. 可预测的停顿时间 2. 没有内存碎片(整理算法) 3. 空间整合效率高 4. 适合大堆(6GB+) 劣势: 1. 内存占用高(RSet等额外结构) 2. 小堆(<4GB)性能不如CMS 3. 程序运行时额外负载(维护RSet)

适用场景:

  • 大内存应用(6GB以上)
  • 需要可预测停顿时间
  • 替代CMS的首选方案
  • JDK9+的默认收集器

2.6 ZGC收集器(JDK11+)

设计目标:

  • 停顿时间不超过10ms
  • 支持TB级别堆
  • 对吞吐量影响<15%

核心技术 - 着色指针(Colored Pointer):

64位对象引用: +--------+--------+--------+--------+ | 未使用 | 标记位 | 重映射位 | 对象地址 | | 16bit | 4bit | 1bit | 42bit | +--------+--------+--------+--------+ 支持16TB堆空间(2^42 = 4TB, Linux支持4倍) 标记位用途: - Marked0: 第一次标记 - Marked1: 第二次标记 - Remapped: 是否已重映射 - Finalizable: 是否只被finalizer引用

核心技术 - 读屏障(Load Barrier):

// 每次从堆中读取对象引用时插入读屏障Objectobj=object.field;// 转换为:Objectobj=barrier(object.field);// 读屏障作用:// 1. 检查对象是否在重定位集合中// 2. 如果是,返回新地址// 3. 更新引用指向新地址

工作流程:

1. 并发标记(Concurrent Mark) 与应用线程并发执行 使用着色指针标记存活对象 2. 并发预备重分配(Concurrent Prepare for Relocate) 统计需要回收的Region 3. 并发重分配(Concurrent Relocate) 复制存活对象到新Region 通过读屏障处理并发访问 4. 并发重映射(Concurrent Remap) 修正所有指向旧对象的引用 可与下一次标记阶段合并

参数配置:

-XX:+UseZGC# 启用ZGC-Xmx16g# 最大堆16GB-XX:ConcGCThreads=4# 并发GC线程数-XX:ZCollectionInterval=120# GC间隔120秒-XX:ZAllocationSpikeTolerance=2# 分配峰值容忍度

性能数据:

测试环境: 128GB堆,40核CPU ZGC: - 停顿时间: 1-3ms (99.9分位) - 吞吐量: 92-95% G1: - 停顿时间: 50-200ms - 吞吐量: 95-98% CMS: - 停顿时间: 100-500ms - 吞吐量: 93-96%

适用场景:

  • 超大堆内存应用(16GB+)
  • 对延迟极度敏感(如金融交易)
  • 实时系统

2.7 Shenandoah GC(JDK12+)

设计理念:与ZGC类似,但实现不同

核心技术 - 转发指针(Forwarding Pointer):

// 在对象头中添加转发指针ObjectHeader:+----------------+------------------+|MarkWord|ForwardingPtr||(已有)|(新增)|+----------------+------------------+// 并发移动时:// 1. 在对象头设置转发指针// 2. 读写屏障检查转发指针// 3. 重定向到新位置

核心技术 - 读写屏障:

// ZGC只有读屏障// Shenandoah有读写屏障// 写屏障示例:object.field=value;// 转换为:barrier_write(object,field,value);// 作用: 保证并发移动时的正确性

参数配置:

-XX:+UseShenandoahGC# 启用Shenandoah-Xmx16g -XX:ShenandoahGCHeuristics=adaptive# 自适应启发式-XX:ShenandoahGCMode=normal# GC模式

对比ZGC:

相同点: - 目标停顿时间都在10ms以内 - 都支持TB级堆 - 都使用并发整理 不同点: 1. ZGC使用着色指针,Shenandoah使用转发指针 2. ZGC只有读屏障,Shenandoah有读写屏障 3. ZGC需要Linux特定支持,Shenandoah跨平台性更好

三、垃圾收集器选择策略

3. 如何为应用选择合适的垃圾收集器?

决策树:

应用类型? │ ├─ 客户端应用/小内存(<100MB) │ └─> Serial GC │ ├─ 服务端应用 │ │ │ ├─ 堆内存 < 4GB │ │ │ │ │ ├─ 吞吐量优先 │ │ │ └─> Parallel GC │ │ │ │ │ └─ 响应时间优先 │ │ └─> CMS GC (JDK8) │ │ G1 GC (JDK9+) │ │ │ ├─ 堆内存 4-32GB │ │ └─> G1 GC │ │ │ └─ 堆内存 > 32GB │ │ │ ├─ 停顿时间要求极低(<10ms) │ │ └─> ZGC / Shenandoah │ │ │ └─ 停顿时间要求一般 │ └─> G1 GC

实战选择建议:

# 1. 电商网站(8GB堆,响应时间<100ms)-XX:+UseG1GC -XX:MaxGCPauseMillis=100# 2. 批处理系统(16GB堆,吞吐量优先)-XX:+UseParallelGC -XX:GCTimeRatio=99# 3. 实时交易系统(64GB堆,延迟<10ms)-XX:+UseZGC
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/15 11:26:52

灰色按钮加强版

链接&#xff1a;https://pan.quark.cn/s/89fddd1af65a灰色按钮能够突破灰色按钮权限&#xff0c;电脑上有许多按钮都是灰色不能够按的&#xff0c;使用了这款软件就变成可以按的啦&#xff0c;欢迎各位前来下载使用&#xff01;使用说明把“按钮使能和”按钮可见“两个选项勾上…

作者头像 李华
网站建设 2026/4/16 10:19:17

华为OD机考双机位C卷- 分月饼 (Java Python JS C++ C )

最新华为上机考试 真题目录:点击查看目录 华为OD面试真题精选:点击立即查看 华为OD机考双机位C卷- 题目描述 中秋节,公司分月饼,m 个员工,买了 n 个月饼,m ≤ n,每个员工至少分 1 个月饼,但可以分多个, 单人分到最多月饼的个数是 Max1 ,单人分到第二多月饼个数是…

作者头像 李华
网站建设 2026/4/16 10:17:46

万字亿集流量系统架构总结笔记(上)

亿集流量自己的提取 注&#xff1a; 这里感觉是对需求的探讨和技术选型的分析&#xff0c;但是不一定就有最推荐的具体设计&#xff0c;更多的是提供思路。 这里有的东西并不具有通用性的服务设计&#xff0c;所以仅讲解 基本通识&#xff0c;不做具体设计。 还有这里感觉我更…

作者头像 李华
网站建设 2026/4/16 10:21:08

直觉模糊不确定性建模与应用【附代码】

✅ 博主简介&#xff1a;擅长数据搜集与处理、建模仿真、程序设计、仿真代码、论文写作与指导&#xff0c;毕业论文、期刊论文经验交流。 ✅成品或者定制&#xff0c;扫描文章底部微信二维码。 (1)基于包含度的粗糙直觉模糊集理论与图像增强 直觉模糊集通过隶属度、非隶属度和…

作者头像 李华