
这套“JVM 20卷”不是让你背字典而是帮你把散落的知识点串成一张网。面试官问JVM本质上不是考你记没记住某个参数而是看你能不能讲清楚“内存怎么分、对象怎么活、垃圾怎么回收、线上怎么排查”。这篇文章我按20卷的形式拆成六大块每卷对应一个必考主题每个主题都从“面试官为什么问”切入再配合原理、对比和实操经验保证你读完不只是会背还能讲得头头是道。1. 这套“20卷”该怎么用先画知识地图再谈背诵很多同学准备JVM面试上来就抱着《深入理解Java虚拟机》啃啃到第十天还在第一章最后发现面试官问的跟书上的重点完全不是一回事。原因很简单——书是写给开发者系统学习用的面试是抽查你核心概念的深度和横向关联能力的。这两者的节奏完全不同。所以我把JVM面试涉及的核心考点抽成了20个必考卷每个卷对应一个独立主题卷与卷之间又有逻辑递进。你在复习的时候不要按顺序死记硬背而是先看这张地图搞清楚自己在哪个位置。卷号核心主题面试常见问法卷一JDK、JRE、JVM关系JVM和JRE有什么区别JDK包含哪些部分卷二JVM生命周期与架构JVM启动流程是怎样的一个Java进程怎么跑起来的卷三类加载过程加载、验证、准备、解析、初始化分别做了什么卷四双亲委派模型为什么需要双亲委派如何打破卷五运行时数据区堆、栈、方法区各自存什么线程私有还是共享卷六栈帧与对象布局栈帧里有哪些结构对象头里有什么卷七对象创建与分配new一个对象完整过程对象一定分配在堆上吗卷八对象存活判定引用计数和可达性分析有什么区别finalize靠谱吗卷九垃圾回收算法标记清除、标记复制、标记整理各自优缺点卷十分代收集理论为什么分代新生代老年代如何配合卷十一经典收集器Serial、ParNew、Parallel各自场景卷十二CMS收集器CMS有哪些阶段什么是并发模式失败卷十三G1收集器Region布局是核心记住集、SATB、Mixed GC如何工作卷十四ZGC与未来ZGC为什么能做到低延迟染色指针是什么卷十五JVM核心参数堆、栈、元空间、GC日志参数怎么配卷十六GC日志分析与调优思路如何看懂一次Full GC日志卷十七JVM故障排查工具jps、jstat、jmap、jstack、jcmd怎么用卷十八OOM与线上事故排查OOM有哪些类型完整排查链路是什么卷十九JMM与并发底层Java内存模型与JVM内存模型的区别happens-before是什么卷二十综合场景题为什么CPU飙升接口偶尔超时和GC有什么关系看完这张表你应该能感觉到前四卷是“地基”中间卷是“核心机制”后面几卷是“实战能力”。面试官通常从任意一卷切入然后向相邻卷发散。你准备的时候围绕每一卷准备一个“30秒定场答案”和一个“3分钟深入答案”这样不管面试官怎么问你都能接得住。2. 基础卷卷一至卷四JDK、JRE、JVM的三角关系与类加载体系2.1 卷一JDK、JRE、JVM到底谁包含谁这道题热度常年排在前三不是因为它难而是因为很多人答到一半就含糊了。先把结论摆在前面JDK包含了JREJRE包含了JVM但JRE除了JVM还包括一组基础类库和启动工具JDK除了JRE还包括编译、调试等开发工具。用大白话理解JVM是“发动机”负责把字节码跑起来JRE是“发动机加基础配件”提供了运行Java程序所需的最小环境JDK是“整车生产线”不仅包含发动机和配件还包含修车工具javac、jdb、javap等。面试时能加分的是补充一句Java是跨平台语言靠的正是“不同平台实现各自JVM”这一设计。你在Windows上编译出的.class可以在Linux的JVM上运行但JVM本身是不跨平台的——这就是“一次编译、到处运行”的代价与实现方式。另一个容易被追问的细节是现代JDK提供了jlink工具可以裁剪出仅包含所需模块的运行时镜像。也就是说理论上你可以构建一个不含某些基础模块的精简JRE这种瘦身在容器化部署场景里非常实用。面试时提到这一层会让面试官觉得你对部署形态有真实感知。2.2 卷二一个Java进程是怎么启动的这个问题看起来简单但很多人答不出层次。最低限度的回答是执行java命令读取.class或入口类调用JVM启动器创建虚拟机实例加载主类执行main方法。但更好的回答应该按这个顺序展开java命令会启动一个JVM进程这个进程是操作系统的普通进程有自己的内存空间。启动器解析启动参数比如-Xmx、-Xms、-classpath然后创建JVM内存结构。JVM通过类加载器加载主类完成链接验证、准备、解析和初始化。调用主类的main方法参数为命令行传入的String[]。当main方法结束后如果没有任何非守护线程存活JVM进程退出。这里面试官常延伸问“main线程结束进程就一定退出吗”。答案是否定的——只要还有非守护线程在运行JVM就不会退出。这也是为什么某些后台任务线程没正确关闭时应用明明“表面上结束”了却仍然占用端口的原因。2.3 卷三类加载的五个阶段面试最爱追问“准备阶段”类加载五个阶段是“加载、验证、准备、解析、初始化”。很多人能背出名字但仔细一问就露馅。重点在两个阶段准备阶段是给类的静态变量分配内存并设置默认值的阶段。注意这里设置的是零值而不是你在代码里写的初始值。比如private static int count 10;在准备阶段count的值是0真正的10是在初始化阶段赋值。初始化阶段才真正执行类构造器clinit()方法这时候静态变量才会被赋上代码里的值静态代码块也会在这里执行。这里有个高频陷阱如果类没有静态变量和静态代码块就不会生成clinit()方法那么初始化阶段可能没有实际操作。另外注意“初始化”和“实例化”是两回事。初始化是类的初始化实例化是new对象。一个类只会初始化一次但可以被实例化无数次。面试官问你“什么时候会触发类初始化”标准答案包括new对象、访问静态字段、调用静态方法、反射调用、初始化子类触发父类初始化、作为JVM启动时的主类等。2.4 卷四双亲委派是怎么委派的以及为什么要打破它双亲委派模型是JVM面试的“必考常量”。一句话概括当一个类加载器收到加载请求时它先不自己加载而是交给父加载器一直向上传到启动类加载器只有父加载器反馈无法加载子加载器才自己尝试。这样做的核心目的有两个一是避免类被重复加载同一个类在整个JVM中只需要加载一次二是保证核心类库的完整性和安全性比如你写一个java.lang.String即使编译通过也无法替换JDK自带的String因为启动类加载器会优先加载rt.jar里的String。面试官接下来十有八九会问“怎么打破双亲委派”。三个经典场景要记住SPI机制JDBC等场景下DriverManager由启动类加载器加载但实际驱动实现却在classpath下启动类加载器加载不到于是引入Thread.currentThread().getContextClassLoader()作为桥梁让父加载器请求子加载器加载。这就是“逆向委派”。Tomcat的WebAppClassLoader每个Web应用一个类加载器优先加载/WEB-INF/classes下的类实现应用隔离。这也是为什么Tomcat里不同应用可以放同一个类的不同版本。自己重写loadClass方法最简单直接的打破方式也是很多框架热部署的基础。有些框架还会先从本地路径加载再走双亲委派。最容易丢分的是“为什么破坏双亲委派还能保证安全”——你要答清楚打破的是加载顺序但类加载流程中的验证、安全机制仍然生效核心类库的加载依然走启动类加载器。3. 内存与对象卷卷五至卷十数据区的每个角落和垃圾回收的道与术3.1 卷五运行时数据区线程私有和线程共享分别有哪些运行时数据区是JVM面试的根本盘这个答不清楚后面全崩。内存区域划分如下线程私有区程序计数器PC寄存器当前线程执行的字节码行号指示器。线程切换后能恢复到正确执行位置。唯一一个不会出现OOM的区域。虚拟机栈描述Java方法执行的线程内存模型每调用一个方法就创建一个栈帧。本地方法栈为JVM使用到的Native方法服务。线程共享区堆存放对象实例绝大多数对象都在这里分配也是GC工作的主战场。方法区存储类信息、常量、静态变量、JIT编译后的代码。JDK8之后用元空间Metaspace实现使用本地内存。面试常有个引导性追问“JDK8为什么把永久代换成元空间”核心答案是永久代大小固定容易触发OutOfMemoryError而且很难调优元空间使用本地内存默认只受物理内存限制大幅降低了OOM概率并且原来永久代里的字符串常量等也做了调整。补充一个常见误区很多人把“JVM内存模型”当成“运行时数据区”的另一个说法其实不对。一个是线程栈和堆的具体划分一个是定义多线程读写共享变量时的内存可见性规则JMM即Java内存模型两者在面试里会分别考察后面卷十九再展开。3.2 卷六栈帧、对象头、访问定位虚拟机栈中的每个栈帧包含四部分局部变量表、操作数栈、动态连接、方法返回地址。局部变量表以槽为单位最小槽是32位long和double占两个槽。操作数栈是计算的工作区比如执行a b先把a压入栈再把b压入栈然后执行加法指令从栈里弹两个值算完结果再压回去。画这个图能讲清楚“为什么虚拟机的计算这么像古老的栈式CPU”。对象在堆中的内存布局有两块重点普通对象头包含Mark Word存储哈希码、GC分代年龄、锁状态标记和类型指针指向类的元数据如果对象是数组还有一个数组长度字段。这里面试官可能会问“为什么对象头里要有分代年龄”——因为新生代对象每熬过一次Minor GC年龄加1到达阈值默认15就晋升老年代这个年龄就存在Mark Word里。对象访问方式有两种句柄访问和直接指针访问。HotSpot默认是直接指针访问好处是速度快、少一次间接寻址句柄访问的好处是对象移动时引用不用修改只需改变句柄指针。大部分时候答“HotSpot用直接指针”就够了但如果面试官比较深还愿意提一句GC时对象移动对这两种方式的影响就是加分项。3.3 卷七对象创建从类加载到内存分配“new一个对象到底发生什么”是综合题。完整链路是检查能否在常量池中定位到类的符号引用并检查这个类是否已加载、链接、初始化过。如果没有则触发类加载。为对象分配内存。这里有两种分配方式指针碰撞适用于堆内存规整复制整理算法后的场景空闲列表适用于内存不规整标记清除算法后的场景。分配时还要考虑并发安全常见手段是CAS失败重试或TLABThread Local Allocation Buffer。内存空间清零确保实例字段在不赋初值时能直接读取到默认零值。设置对象头信息比如对象属于哪个类、哈希码、GC分代年龄。执行init()构造方法按程序员的意愿初始化对象。面试官很爱追问“对象一定在堆上吗”。现代JVM有逃逸分析如果对象不会被外部访问可能被优化到栈上分配甚至直接标量替换成基本类型。还有一种情况是TLAB——虽然内存是从堆上申请的但为每个线程预分配了一块缓冲区分配对象时无需锁这也是提升性能的重要手段。3.4 卷八对象存活的判定方式判断对象是否可回收有引用计数和可达性分析两种思路。引用计数算法简单直观但解决不了循环引用问题。比如A引用了BB引用了A但外部没有引用指向它们这两个对象引用计数永远不为0就泄漏了。当然现代JVM不用引用计数但JVM之外部分语言比如Python还在用所以面试官希望你能答出“引用计数的致命缺点”和“可达性分析如何避免这个问题”。可达性分析是从GC Roots出发向下搜索引用链没被引用链覆盖到的对象就是可回收对象。GC Roots包括虚拟机栈中引用的对象、静态属性引用的对象、常量引用的对象、本地方法栈中JNI引用的对象、Java虚拟机内部的引用等。这里要特别记一个不再是强引用的对象即使被引用也可能被回收。顺带着引出了四种引用类型强引用OOM也不回收、软引用内存不足时回收、弱引用每次GC都会被回收、虚引用随时可被回收主要用于跟踪对象被回收的通知。面试常结合缓存场景问比如“用弱引用做缓存行不行”——答案是不靠谱因为弱引用生命周期太短一GC就没缓存命中率无法保证一般还是用软引用做内存敏感缓存或配合ReferenceQueue做资源清理。3.5 卷九到卷十三种GC算法和分代假设GC算法的演进是一条清晰的线。标记-清除是最基础的先标记可回收对象再统一回收。优点是实现简单缺点是会产生大量内存碎片而且标记和清除两个过程效率都不高。标记-复制把内存按容量分成两块每次只用一块回收时把存活对象复制到另一块然后整块清理。优点是实现简单、运行高效、无碎片缺点是内存浪费——可用内存只有一半。标记-整理则是在标记后让所有存活对象向一端移动然后直接清理掉端边界以外的内存兼顾了空间利用率和无碎片但移动对象需要更新引用开销更大。现代JVM采用分代收集理论新生代对象朝生夕灭适合复制算法老年代对象存活率高适合标记-清除或标记-整理。HotSpot新生代就是这种设计Eden区占8份两个Survivor各占1份每次使用Eden和一块Survivor回收时把存活对象复制到另一块Survivor这就确保新生代只有10%的空间是闲置的复制算法的空间浪费被大幅压缩。“为什么需要安全点”也在这附近被问到。GC发生时不是所有线程都能随便停下来必须运行到安全点SafePoint才可以挂起。安全点通常选在方法调用、循环跳转、异常跳转等位置。如果程序长时间执行不进入安全点比如一个死循环就会拖住整个GC。这类和“Stop The World”相关的知识点建议和GC日志、线上停顿一起看效果更好。4. 收集器卷卷十一至卷十四从Serial到G1再到ZGC面试官爱问的分水岭4.1 卷十一经典收集器不是基础题是选型题Serial是单线程收集器GC时必须暂停所有工作线程但简单高效适合客户端模式或单核环境。ParNew是Serial的多线程版本常用于服务端配合CMS。Parallel Scavenge的目标是达到可控的吞吐量说直接点就是“在乎跑完一个任务需要多久”。虽然这道题本身不难但面试官真正想听的不是你罗列参数而是根据场景选型的能力。比如你部署一个低延迟交易系统你会选Parallel还是CMS一个内部离线计算任务吞吐量优先你又怎么选面试前如果想不出来可以记一条经验策略交互型应用优先低延迟选停顿时间短的计算型应用优先高吞吐选总运行时间短的。4.2 卷十二CMS的六个阶段和“并发模式失败”CMS全称Concurrent Mark Sweep目标是“低停顿”。它一共六个阶段初始标记STW很快、并发标记、并发预清理、重新标记STW较短、并发清理、并发重置。初始标记只找GC Roots直接关联的对象并发标记和用户线程一起跑重新标记修正并发期间的变动并发清理真正开始回收。CMS的经典缺点是“并发模式失败Concurrent Mode Failure”。当并发清理时老年代垃圾产生速度快于清理速度CMS会退化为Serial Old进行Full GC导致很长的停顿。这也是为什么设置CMS时通常会让老年代在阈值70%左右就开始回收留出缓冲。CMS的另一个问题是它基于标记-清除会产生碎片长时间运行下来碎片化严重最终不得不Full GC。面试官问“为什么CMS被G1取代”答三点就够了长时间运行碎片严重、停顿时间不可预测、在Java 9以后被标记为废弃。不过CMS的并发收集思想仍然值得学因为G1和ZGC继承了这种思路。4.3 卷十三G1是面试分水岭这几个机制必须吃透G1是当前面试出现频率最高的收集器因为它把“堆不再分物理连续代”变成“堆被划分为很多个Region”。每个Region既可能是Eden、Survivor、Old也可能被回收后重新分配。这种逻辑上的分代让G1能从整体上维护优先回收列表——优先回收那些“垃圾最多”的Region这就是G1的Garbage First名字来源。G1的另一大创新是记忆集Remembered Set。因为Region之间会互相引用GC扫描某个Region时不能只扫它自己还要知道别的Region中有谁引用了它。RSet就是为了解决跨Region引用问题代价是占用额外内存所以G1的RSet大小需要结合应用的内存容量来评估。新生代回收用STABSnapshot-At-The-Beginning算法它通过写屏障在并发标记开始时记录对象图的快照保证并发标记过程里由于用户线程修改引用而漏标的对象也能被捕获。这个细节面试经常问到你只要记着“它牺牲了一点精确度来换取并发标记的稳定性和吞吐”就不会答错方向。G1的Mixed GC会分阶段回收老年代Region真正找到“停顿时间与吞吐的平衡点”。面试时我建议你这样串讲G1先认识Region布局再用RSet解决跨Region引用用SATB捕捉并发标记期间的引用变化最后通过可预测的停顿模型选择哪些Region参与回收。4.4 卷十四ZGC为什么敢说“几乎不卡顿”ZGC的目标是把暂停时间控制在10毫秒以内核心手段是染色指针和读屏障。染色指针把少量信息比如Mark、N44、MMAP等直接编码在指针里而不需要访问GC元数据。这样GC在移动对象时可以通过读屏障感知并修正指针。通俗解释一下ZGC的回收过程跟用户线程并发跑用户线程读对象时通过读屏障检查指针里的状态如果对象被移动了读屏障会重定向到新地址。整个过程用户线程几乎不用停。ZGC支持TB级别堆在超大堆场景下比G1更稳。面试问到“ZGC有什么缺点”可以答CPU开销更高因为读屏障在每次读引用时都有额外成本在JDK15之前不支持分代回收所以对于对象生命周期很短的应用效率不如G1JDK21后ZGC才加入分代支持。5. 调优与实战卷卷十五至卷十八参数、日志、工具、线上事故排查5.1 卷十五核心参数分类别再背什么“8G内存配多大堆”的现成答案调优参数题最怕的是“背答案”。面试官问你“堆内存怎么设置”你说“-Xmx8g -Xms8g”只能拿一半分你要说清楚为什么这么配。堆参数分几类初始堆大小-Xms、最大堆大小-Xmx通常建议生产环境两者设置相等避免堆动态伸缩带来的性能波动新生代大小-Xmn或者通过-XX:NewRatio设置老年代和新生代比例-XX:SurvivorRatio设置Eden和Survivor的比例元空间-XX:MaxMetaspaceSize限制大小线程栈-Xss设置单线程栈大小默认在Linux x64下通常是1MB如果线程数很多调小栈容量可以降低内存占用比如256KB。理解这些参数的关键是“算一笔账”。比如你有一个4核8G的容器跑一个Web应用通常建议堆内存给到系统内存的50%左右预留一些给元空间、线程栈、JIT编译器、GC相关结构和操作系统。不是无脑把-Xmx开满——开满会导致容器OOMKilled或触发系统内存交换性能反而急剧下降。5.2 卷十六GC日志怎么看一张表格说明白很多候选人看GC日志处于“能看懂Full GC字样但不知道和线上问题怎么关联”的状态。这里给一个观察框架先看频率再看单次耗时再看回收前后内存变化最后看是Minor GC还是Full GC。常规启动参数可以加上-XX:PrintGCDetails -XX:PrintGCDateStamps -Xloggc:/path/gc.log一段典型的G1日志长这样[GC pause (G1 Humongous Allocation) (young) (initial-mark) 2024-01-01T00:00:01.0000000: 1.000: [GC pause (young) (initial-mark), 0.0057310 secs] [Parallel Time: 1.2 ms, GC Workers: 8] [GC Worker Start (ms): Min: 1.0, Avg: 1.0, Max: 1.0, Diff: 0.1] [Ext Root Scanning (ms): Min: 0.2, Avg: 0.3, Max: 0.5, Diff: 0.3] [Update RS (ms): Min: 0.1, Avg: 0.2, Max: 0.4, Diff: 0.3] ... [Code Root Fixup: 0.0 ms] [Clear CT: 0.1 ms] [Other: 0.6 ms] [Choose CSet: 0.1 ms] [Ref Proc: 0.3 ms] [Redirty Cards: 0.2 ms] [Eden: 512.0M(512.0M)-0.0B(512.0M) Survivors: 64.0M-64.0M Heap: 1.2G(4.0G)-688.0M(4.0G)] [Times: user0.02 sys0.00 real0.01 secs]重点看两个位置一是Eden和Survivor的变化说明此次回收清了哪些区域二是Heap前后的变化看看回收后堆占用是否真的降下来了。如果每次GC回收完内存还是迅速涨满说明对象分配速率远高于回收速率这时候要从代码层面排查是否有大对象泄漏而不是一味调大堆。5.3 卷十七jps、jstat、jmap、jstack的实战用法面试到工具题光背命令名字没用要能说出什么场景用什么命令、怎么看输出、怎么定位问题。我列一下最常用的五个工具和典型场景jps列举Java进程类似ps但是只针对Java进程。比如jps -l能打印完整主类名。jstat查看JVM状态重点是jstat -gcutil pid 1000每隔一秒输出一次GC统计观察Eden、Old、Metaspace的使用百分比以及YGC、FGC的次数和耗时。jmap导出堆快照或者查看堆信息。jmap -dump:formatb,fileheap.hprof pid导出dump文件再用MAT或VisualVM分析。jstack打印线程快照通常用于排查CPU飙高、线程死锁、长时间卡顿的调用栈。jstack pid后配合top -H -p pid找到占用CPU高的线程ID注意转成十六进制再到jstack输出里查对应的栈帧。jcmd一个综合工具可以替代很多旧命令。比如jcmd pid GC.heap_info。建议你自己在测试环境压一个接口然后用jstack抓一下线程状态亲眼看WAITING、RUNNABLE、BLOCKED这些状态长什么样面试聊起来会自然得多。5.4 卷十八OOM类型和一次完整的事故排查链路OOM可以分为几类java.lang.OutOfMemoryError: Java heap space堆溢出、GC overhead limit exceededGC时间过长但回收效果差、Metaspace元空间溢出、unable to create new native thread无法创建线程。我在生产环境遇到过最典型的一种服务每天固定时段报“Java heap space”。排查链路建议如下先确认是不是真的堆不足。用jstat观察FGC次数和GC后占用如果GC后占用没降下来多半是存在对象泄漏。用jmap导出堆快照然后用MATMemory Analyzer Tool或Eclipse MAT打开看Dominator Tree找占用最大对象。找到大对象后分析引用链定位到具体业务代码。通常会发现是一个全局缓存无上限增长或者一个分批处理任务把大量数据查出来塞进了集合。修复后再通过压测观察GC趋势判断是否真的解决。注意一个反直觉的坑有时候OOM不一定是堆小而是代码里创建了太多大数组、大缓存、或者线程数失控。所以第一步永远是“看现场”而不是一上来就调大-Xmx。6. 压轴卷卷十九、卷二十JMM并不属于JVM内存模型是两个体系6.1 卷十九JMM与并发三特性卷十九的核心是区分两个概念JVM运行时数据区和Java内存模型JMM。很多人在面试时混着讲导致后面一聊并发就说不清。JMM是Java规范里定义的一套抽象模型用来解决多线程共享变量的可见性问题、有序性问题和原子性问题。它规定所有共享变量存在主内存每个线程有自己的工作内存线程对变量的操作必须“先拷贝到工作内存、操作完再写回主内存”。这跟CPU缓存和主内存的关系非常像——这也是为什么讲并发都会拿CPU缓存结构来类比。面试的三连问通常是原子性i为什么不是原子操作因为读取、计算、写回三个步骤可能被打断。可见性为什么一个线程修改了变量另一个线程不一定马上看得到因为工作内存和主内存之间存在同步时差。有序性为什么代码重排会导致诡异结果因为编译器和CPU为了优化会调整指令顺序而单线程内没问题、多线程下就可能出问题。happens-before规则是JMM中比较重要的一个概念。它的核心含义是如果操作A happened-before 操作B那么A的结果对B可见。八大规则里重点记程序次序规则、锁规则、volatile变量规则、线程启动/中断/终止规则、传递性。其中锁规则和volatile变量规则是面试中出现频率最高的。6.2 卷二十综合场景题的答题思路到了卷二十面试官会把前面所有卷的知识放进一个真实场景里考你。比如题目一线上Java服务CPU飙高到100%如何排查第一步用top找到CPU高的Java进程第二步top -H -p pid找到具体线程第三步把线程ID转成十六进制printf %x\n tid用jstack输出找到对应线程栈第四步定位到具体业务代码分析为什么占用CPU常见的可能包括死循环、频繁GC、正则回溯、锁竞争。注意CPU飙高不一定是业务方法本身慢也可能是GC线程疯狂工作所以jstat也要看一眼GC状态。题目二老年代一直增长FGC频繁怎么办先判断是内存泄漏还是内存分配速率过高。最有效的方案是导出堆快照查看对象直方图和Dominator Tree。如果是大量相同类型对象基本可以定位到那个业务方法。同时配合GC日志看每次Full GC前老年代的占用趋势是逐步爬升还是突然暴涨两种形态对应的原因完全不同。题目三为什么我设置-Xmx4g实际进程占的内存远大于4g这是压轴题里最让人舒服的一道因为它考察的是“堆外内存”的理解。JVM进程占用的RSS内存不仅包含堆还包含Metaspace、线程栈、JIT编译器代码缓存、GC结构RSet、Card Table、DirectByteBuffer的堆外内存、以及JNI分配的内存。所以容器里部署Java服务时千万不要只给-Xmx加一点点余量。建议至少在-Xmx之外预留20%到30%的系统内存否则很容易被底层监控判定为内存紧张甚至OOMKilled。7. 我的学习路线和一点实战经验最后分享一套我个人验证过很有效的备考方式。不要一上来就追求把所有细节背熟而是按“三轮复习法”来第一轮3-5天搭框架。把上面20卷的所有主题浏览一遍确保每个卷都能用两到三句话讲清楚“是什么、为什么、怎么用”。这轮不求记住参数只求脑子里有张地图。第二轮3-5天深挖高频内容。围绕双亲委派、内存模型、G1、GC日志、OOM排查这五个方向做细致复习每个方向各准备一个“3分钟讲解”并且用实际工具操作一遍。比如自己写个死循环的demo然后jstack去看线程栈。这一步不是为了学会工具而是为了让你对“命令输出的样子”有真实记忆。第三轮2-3天模拟提问。找一份JVM面试题清单对着镜子或者开着录音答一遍。重点不是“背对答案”而是练习“从题目跳到知识地图的哪个卷”——当你能在十秒钟内定位到考点是卷四还是卷十三你就算真正掌握了。说句大实话JVM面试题看着多但绝大多数公司不会问你“哪个参数控制对象年龄阈值”这种极端冷门的问题。一般围绕五个核心问类加载机制、内存分区、GC原理和收集器、线上调优排查、并发内存模型。把这五个核心吃透再配合两三个真实的故障案例细节效果远胜于背三十个问答题。你在复习过程中如果遇到“讲着讲着发现前后矛盾”那是好事——说明你真的在理解它而不是在假装理解它。