上篇并发聊完CountDownLatch和ConcurrentHashMap,这篇进入JVM专项。第28篇讲过JVM内存和GC基础,这篇深入——G1和ZGC怎么工作?线上OOM怎么排查?MAT怎么分析?
P7面试JVM考的不是"你知道哪些GC算法",而是"频繁Full GC怎么排查?G1的Region怎么分配?ZGC怎么做到毫秒级停顿?"
今天8道题覆盖P7 JVM核心考点。
Q1:G1收集器原理?和CMS什么区别?
G1(Garbage First):把堆分成多个等大的Region(默认约2048个),Region可以是Eden/Survivor/Old/Humongous(大对象专用)。不再像CMS那样新生代和老年代物理隔离。
Young GC:回收所有Eden和Survivor Region。存活对象复制到空Survivor Region。STW(Stop The World)暂停。
Mixed GC:回收新生代Region + 部分老年代Region(选回收收益高的Region,这就是"Garbage First"的由来)。老年代Region的回收基于Remember Set(记录跨Region引用)。
G1 vs CMS:G1可预测停顿时间(-XX:MaxGCPauseMillis=200),CMS不能保证。G1用Region复制不会有碎片问题,CMS用标记-清除有碎片。G1适合大堆(>6GB),CMS在小堆(<4GB)延迟更低。
追问:G1的Remember Set是什么?每个Region维护一个RSet,记录"谁引用了我这个Region里的对象"。回收时只需扫描RSet而不是全堆扫描,大幅减少GC时间。RSet的代价是写屏障(每次引用修改都要更新RSet)。
Q2:ZGC原理?怎么做到毫秒级停顿?
ZGC(Z Garbage Collector):JDK 11引入,目标停顿<10ms。把STW工作搬到并发阶段。
着色指针:64位指针拿4位存标记状态。对象引用本身携带标记信息,不需要额外mark bitmap。
读屏障:读引用时检查指针颜色。指向已移动对象则自动修复指针。应用线程和GC线程可并发移动对象。
并发转移:传统GC移动对象必须STW。ZGC用着色指针+读屏障实现并发移动。
追问:ZGC和G1怎么选?堆<4GB用G1(ZGC有额外的读屏障开销)。堆>4GB且对延迟敏感用ZGC。JDK 17后ZGC更成熟,推荐作为默认选择。
Q3:GC调优怎么做?关键参数?
GC日志:-Xlog:gc*:file=gc.log 输出每次GC的详细信息(类型、耗时、回收前后堆大小)。分析工具:GCEasy(在线)、GCViewer。
关键参数:-Xms/-Xmx设一样避免堆扩缩。-XX:NewRatio=2老年代:新生代=2:1。-XX:MaxGCPauseMillisG1目标停顿。-XX:G1HeapRegionSizeRegion大小(1-32MB)。
调优步骤:看GC日志确认问题 → 调堆大小和比例 → 调G1参数 → 压测验证。数据驱动不盲目调参。
追问:Full GC频繁怎么排查?常见原因:老年代空间不足(大对象直接进入老年代)、Metaspace满(类太多)、System.gc()调用(某些三方库触发)、G1的Evacuation Failure(新生代Region不够用)。看GC日志的cause字段定位。
Q4:OOM有哪些类型?分别什么含义?
Java heap space:堆不够。对象太多或太大。最常见。
GC overhead limit exceeded:98%时间做GC回收不到2%内存。本质是内存泄漏。
Metaspace:类元数据太多。动态代理/反射生成大量类。
Direct buffer memory:NIO的堆外内存超限。Netty/NIO框架常见。
unable to create new native thread:线程数超限。线程池配置不当或泄漏。
Q5:线上OOM怎么排查?
第一步:JVM参数加-XX:+HeapDumpOnOutOfMemoryError,OOM时自动生成hprof文件。
第二步:MAT打开hprof。Leak Suspects Report自动分析疑似泄漏。
第三步:Dominator Tree找内存最大的对象。Histogram看每个类的实例数和占用。
第四步:Path to GC Roots看强引用链——谁持有这个对象导致不能回收。通常是集合忘了clear、回调没取消、Context泄漏。
追问:hprof文件太大(几个GB)打不开怎么办?用jhat命令行分析,或调大MAT内存(MemoryAnalyzer.ini里-Xmx设大)。也可以在服务器上先用jmap -histo:live <pid>看哪些对象多,缩小范围后再下载dump分析。
Q6:MAT怎么用?关键功能?
Histogram:按类统计对象数量。Retained Size比Shallow Size更有意义——Shallow是对象本身,Retained是删掉它后能回收的总量(含引用对象)。
Dominator Tree:支配树顶层节点就是内存占用最大的对象链。
Leak Suspects:MAT自动报告——"线程X持有的HashMap疑似泄漏,占堆60%"。
Path to GC Roots:选中对象看GC Root引用链,排除弱引用找到强引用链即泄漏路径。
追问:MAT里Shallow Size和Retained Size什么区别?一个Bitmap对象Shallow Size可能就几百字节(只存宽高和像素数组引用),但Retained Size是几MB(包括像素数组本身)。排查泄漏看Retained Size。
Q7:Android上怎么做内存分析?
LeakCanary:Square开源的Android内存泄漏检测。原理:Activity destroy后把弱引用放入ReferenceQueue,5秒后检查——如果还在说明泄漏了。自动dump堆并分析泄漏路径,在Debug包直接弹通知。
Android Studio Profiler:实时内存监控(Memory tab),看堆分配和GC频率。Capture Heap Dump直接生成hprof。Allocation Tracking看每次内存分配是从哪行代码来的。
hprof转换:Android的hprof格式和标准Java hprof不同,用hprof-conv转换:hprof-conv input.hprof output.hprof,然后用MAT分析。
追问:LeakCanary怎么定位泄漏对象?通过GC Roots分析引用链——找到从GC Root到泄漏对象的最短强引用路径。告诉你"MainActivity泄漏,因为匿名内部类持有Activity引用"。比手动用MAT分析快十倍。
Q8:ART和JVM的GC什么区别?
ART的GC策略:Android 8.0后用CC GC(Concurrent Copying),类似G1并发复制。堆分Region,新生代复制算法,老年代并发标记。大部分工作并发执行,STW极短(<5ms)。
ART vs JVM:ART安装时全量编译(dex2oat),JVM用JIT运行时编译热点。ART没有Metaspace但LinearAlloc有5MB限制——类太多会crash。
追问:Android App需要关心GC吗?正常情况不需要——系统自动管理。但内存抖动(频繁创建临时对象)会触发频繁GC,GC时STW导致主线程卡顿。解决方案:对象池复用(Message.obtain)、避免在onDraw里创建对象。
面试Tips:P7 JVM专项G1考Region机制和Mixed GC原理。ZGC考着色指针和读屏障。GC调优考参数和GC日志分析。OOM排查考HeapDump+MAT流程。MAT考Retained Size和Dominator Tree。Android内存分析考LeakCanary原理和hprof分析。准备JVM面试推荐《深入理解Java虚拟机》第3版,重点看第3章(GC)和第13章(调优)。
下一篇进入Android Framework专项——Binder驱动原理、AIDL深入、ContentProvider机制、BroadcastReceiver底层。
线上排查过OOM的同学评论区说说,你遇到过最棘手的内存泄漏是什么?
本系列连载中,关注不迷路,下一篇:阿里P7高级Android(Framework专项)面试真题
系列简介:Android大厂面经连载,覆盖字节跳动、腾讯、阿里、美团等40+企业,从初级到架构师全岗位覆盖。每篇文章包含真实面试题+详细答案+代码示例,帮你拿到大厂Offer。