JVM 核心知识 JVM 核心知识一、类加载子系统1.1 类加载完整生命周期JVM 采用懒加载机制类不会在启动时一次性全部加载而是用到才加载、不用不加载节省内存、提升启动速度。完整生命周期加载 → 链接 → 初始化 → 使用 → 卸载阶段详解加载读取磁盘 .class 字节码文件解析为内存数据结构生成 Class 对象。链接分为三步验证校验字节码格式、安全性、合法性防止恶意/错误字节码。准备为static 静态变量分配内存并赋默认初始值0、null、false仅赋系统默认值。解析将代码中的符号引用统一转换为内存直接引用。初始化类加载唯一执行业务逻辑的阶段。JVM 主动执行静态代码块、为静态变量赋予代码中自定义初始值是静态资源真正初始化的阶段。使用程序正常调用类的属性、方法、执行业务逻辑。卸载类无任何引用、类加载器被回收释放元空间内存。1.2 四大类加载器体系1.2.1 启动类加载器Bootstrap ClassLoader顶层核心加载器HotSpot 虚拟机中由C 编写Java 代码无法获取其引用调用获取方法返回null。核心职责加载 Java 底层核心类库rt.jar 等基础核心依赖。加载特点按名索骥非全盘扫描JVM 启动不会遍历加载 lib 目录所有 jar核心类库范围在 JVM 底层 C 源码中硬编码固定。核心安全两道防线防止核心类篡改字节码校验机制若直接替换官方 rt.jarJVM 启动会校验核心类结构、本地方法映射。一旦检测到类结构被篡改、方法缺失直接抛出SecurityException/LinkageError拒绝启动。双亲委派机制禁止自定义核心类覆盖原生类。例如自定义java.lang.String无法生效。双亲委派拦截流程程序加载自定义java.lang.String时应用类加载器优先向上委派依次委派至扩展类加载器、最终抵达启动类加载器启动类加载器检测到核心类库已存在该类直接返回官方原生 String 类自定义核心类彻底失效永远不会被加载执行。1.2.2 扩展/平台类加载器Extension / Platform ClassLoader负责加载 Java 拓展类库、系统扩展依赖承接启动类加载器与应用类加载器的中间委派能力。1.2.3 应用程序类加载器Application ClassLoader又称系统类加载器可通过ClassLoader.getSystemClassLoader()获取。日常开发中所有自定义类、第三方 Jar 包默认由该加载器加载。1.2.4 自定义类加载器Custom ClassLoader实现方式开发者继承ClassLoader类重写findClass()方法。使用场景热部署、代码加密、自定义资源加载、特殊业务类加载需求。1.3 Class 常量池Java 源码编译为 .class 文件后文件内部会生成一张常量池表存储编译期固定数据是运行时常量池的基础。存储内容字面量字符串字面量、final 修饰常量、基本数据类型固定值。符号引用类和接口全限定名、字段名称与描述符、方法名称与描述符。核心作用编译期无法确定类、方法、字段的真实内存地址因此用字符串符号临时占位在类加载解析阶段统一替换为真实内存直接引用。二、执行引擎子系统2.1 核心职责将 .class 字节码指令翻译、优化为操作系统、CPU 可识别的机器指令并执行是 JVM 真正执行业务逻辑的核心模块。2.2 执行架构解释器 JIT 混合模式现代 HotSpot JVM 默认采用混合执行模式兼顾项目快速启动与长期运行高性能。┌──────────────────────────────────────────────────┐ │ 执行引擎子系统 │ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌────────┐ │ │ │ 解释器 │ │ JIT编译器 │ │ 垃圾 │ │ │ │ (Interpreter)│ │ (JIT Compiler)│ │ 收集器 │ │ │ └──────────────┘ └──────────────┘ │ (GC) │ │ │ │ │ └────────┘ │ └───────────┼────────────────┼─────────────────────┘ ▼ ▼ 逐行翻译 热点编译 启动快、执行慢 启动慢、执行极快两者协作机制项目启动初期解释器逐行解释执行字节码无需预编译保证项目秒级启动、快速响应。运行过程中JVM 内置计数器持续探测热点代码高频循环、频繁调用的代码。热点命中后JIT 编译器后台将热点字节码编译、优化为本地机器码并缓存。无缝替换执行后续再次调用该代码直接执行优化后的机器码放弃逐行解释大幅提升运行吞吐量。2.3 GC 模块定位垃圾回收器GC是执行引擎子系统的核心组件后台静默运行自动回收堆内存中失效对象防止内存溢出、内存泄漏。三、垃圾回收 GC 核心体系3.1 七大经典垃圾回收器分类新生代 (Young) Serial ─── Parallel Scavenge ─── ParNew │ │ │ │ │ │ 老年代 (Old) Serial Old ─── Parallel Old ─── CMS 整堆 / 区域化回收 G1 (Garbage First)3.1.1 串行收集器Serial Serial Old工作模式单线程回收GC 期间全程 STW暂停所有用户线程。回收算法新生代复制算法、老年代标记-整理算法。适用场景客户端程序、单核 CPU、极小内存环境几十MB~两百MB。3.1.2 并行吞吐量收集器Parallel Scavenge Parallel Old定位JDK8 默认垃圾回收器。工作模式多线程并行回收依旧存在 STW但利用多核 CPU 大幅缩短回收耗时。核心目标最大化 CPU 利用率追求高吞吐量。适用场景后台批处理、数据计算、无高交互的服务端业务。3.1.3 并发低延迟收集器ParNew CMS核心优势部分 GC 阶段与用户线程并发执行大幅降低停顿时间。核心目标追求低延迟、低停顿。缺点采用标记-清除算法产生内存碎片对 CPU 资源消耗敏感。版本现状JDK9 标记废弃JDK14 彻底移除。3.2 三色标记算法并发 GC 核心原理三色标记是 CMS、G1、ZGC 等现代并发回收器的核心存活判定算法用于并发标记阶段精准区分对象存活状态。[ 黑色 (Black) ] ─── [ 灰色 (Gray) ] ─── [ 白色 (White) ] (自身及下属全完工) (自身完工,下属未遍历完) (未访问 / 垃圾对象)状态定义白色初始状态所有对象默认白色标记结束仍为白色则判定为垃圾。灰色中间过渡状态当前对象已扫描但引用的子对象未全部遍历完成。黑色完全存活对象自身及所有引用子对象全部扫描完毕安全存活不会被回收。3.2.1 正常标记流程初始状态堆内所有对象标记为白色。初始标记从 GC Roots 出发将直接关联的第一层对象标记为灰色。并发标记遍历灰色对象引用将白色子对象依次置灰遍历完成的灰色对象转为黑色。最终状态所有灰色对象全部转正剩余白色对象统一回收。3.2.2 并发标记致命问题对象漏标单 GC 线程标记时三色标记无问题但用户线程与 GC 线程并发执行时会出现漏标问题导致存活对象被误回收。漏标场景还原初始状态黑色对象 A 引用灰色对象 B灰色对象 B 引用白色对象 C。用户线程同时执行两步操作B 断开对 C 的引用B.c null黑色 A 新建引用指向白色 CA.c C。【篡改前】 【篡改后】 [ A (黑) ] [ A (黑) ] ──(新引用)── [ C (白) ] │ ▼ [ B (灰) ] ── [ C (白) ] [ B (灰) ] (已断开引用)问题结果GC 仅扫描灰色对象 BB 无引用 C扫描完毕后 B 变为黑色黑色对象不会二次扫描导致存活对象 C 始终为白色最终被当做垃圾回收引发空指针异常。结论CMS 的重新标记、G1 的最终标记都是为了修复并发漏标问题。3.2.3 CMS 与 G1 漏标修复机制对比1. CMS 重新标记增量更新算法核心逻辑并发期间检测到黑色对象指向白色对象通过写屏障将黑色对象重置为灰色。修复阶段全程 STW批量重新扫描所有被重置的灰色对象引用链。缺点需扫描大量对象甚至连带扫描新生代STW 停顿时间长、性能差。2. G1 最终标记SATB 原始快照算法核心逻辑并发期间检测到灰色对象断开白色对象引用通过写屏障将旧引用存入 SATB 缓冲区并提前将对象置灰。修复阶段STW 仅处理缓冲区少量漏网数据无需全量扫描。优点停顿时间极短、开销可控、性能稳定。3.3 GC 分类体系面试高频3.3.1 部分收集Partial GC① 新生代收集Minor GC / Young GC回收范围仅新生代 Eden、S0、S1 区域。触发条件Eden 区内存耗尽。核心特点对象朝生夕灭触发频繁采用复制算法速度极快STW 耗时仅几毫秒至几十毫秒。② 老年代收集Major GC / Old GC回收范围仅针对老年代区域。触发条件老年代内存空间不足。特点速度比 Minor GC 慢 10 倍以上STW 耗时更长多数场景会伴随 Full GC。③ 混合收集Mixed GCG1 专属回收范围全部新生代 部分垃圾最多的老年代 Region。触发机制老年代内存占用达到阈值XX:InitiatingHeapOccupancyPercent。特点按回收价值筛选区域精准回收可控停顿时间适配大内存服务。3.3.2 整堆收集Full GC最重 GC回收范围新生代、老年代、元空间整堆全量回收。四大核心触发条件必考老年代空间不足对象晋升失败元空间/方法区加载类过多内存耗尽代码主动调用System.gc()建议回收大概率触发 Full GCMinor GC 空间分配担保失败。核心特点全局 STW暂停所有业务线程服务卡顿严重线上调优核心目标减少 Full GC 频次与耗时。四、运行时数据区子系统运行时数据区分为线程共享区全局共用和线程私有区线程隔离、随线程生死两大模块。4.1 线程共享内存区域4.1.1 堆 HeapGC 核心区域JVM 最大内存区域所有对象实例、数组默认在堆内存分配是 GC 主要管理和回收的区域。对象完整内存结构[ 栈 ] [ 堆 (Heap) ] user ──(存储指针)── ┌──────────────────────────────┐ │ 1. 对象头 (Mark Word Klass) │ ├──────────────────────────────┤ │ 2. 实例数据 (id, name, age) │ ├──────────────────────────────┤ │ 3. 对齐填充 (补齐为8字节倍数) │ └──────────────────────────────┘对象创建完整流程类加载检查校验目标类是否已加载未加载则优先执行类加载流程。内存分配堆内存规整采用「指针碰撞」内存碎片多采用「空闲列表」。并发安全保障TLAB为每个线程分配私有缓冲区多线程创建对象互不抢占、避免并发冲突。内存零值初始化对象内存不含对象头默认赋 0、null保证实例变量可直接访问。设置对象头写入哈希码、GC 分代年龄、锁状态、类指针等核心元数据。构造方法初始化执行init方法按业务代码为属性赋值完成对象创建。堆内存分代结构┌────────────────────────────────────────────────────────────┐ │ 堆内存 (Heap) │ ├─────────────────────────────────────┬──────────────────────┤ │ 新生代 (Young) │ 老年代 (Old) │ ├──────────────────┬──────────┬───────┤ │ │ Eden (伊甸园) │ From (S0)│To (S1)│ 长期存活的老对象 │ └──────────────────┴──────────┴───────┴──────────────────────┘新生代对象新手村Eden 区绝大多数新对象的诞生、驻留区域。Survivor S0/S1存活对象中转站。GC 流转规则Eden 满触发 Minor GC存活对象复制至 S0/S1年龄1下次 GC 时Eden 上一轮 Survivor 存活对象转移至另一块 Survivor 区来回流转、年龄递增。老年代对象养老院存储长期存活、生命周期稳定的对象三种晋升机制高龄晋升对象年龄达到阈值默认15可通过-XX:MaxTenuringThreshold修改晋升老年代。大对象直接晋升超大数组、长字符串等大对象新生代无法容纳直接分配至老年代避免频繁复制损耗性能。动态年龄判定Survivor 中同年龄对象总大小超过 Survivor 区域一半该年龄及以上对象直接晋升老年代。垃圾存活判定可达性分析算法HotSpot 虚拟机核心垃圾判定算法以GC Roots为起始节点遍历引用链不可达对象判定为垃圾。GC Roots 核心来源虚拟机栈局部变量表引用的对象方法内正在使用的局部对象方法区静态属性、常量引用的对象字符串常量池中的引用对象本地方法栈 JNI 引用的 Native 关联对象JVM 内部常驻对象Class 对象、系统异常对象、类加载器等。4.1.2 元空间 MetaspaceJDK8JDK8 废弃永久代PermGen采用元空间直接使用操作系统本地内存不占用堆内存。核心存储内容类元信息类名、父类、接口、修饰符、类结构描述字段、方法信息名称、类型、参数、修饰符、方法字节码运行时常量池、字符串常量池引用虚方法表、接口方法表JIT 编译优化缓存、逃逸分析、计数器数据。运行时常量池每个类加载后独立生成存储编译期字面量、符号引用在类加载解析阶段将符号引用动态翻译为内存直接引用。字符串常量池位置变迁JDK7 前在方法区JDK7 及以后迁移至堆内存特性全局共享、自动去重创建字符串时优先查询常量池存在则复用引用不存在则新建对象。虚方法表vtable / itablevtable存储所有可重写方法的内存地址支撑多态动态绑定itable存储接口方法的具体实现地址方法调用时直接查表定位指令入口执行效率极高。4.2 线程私有内存区域线程私有区域随线程创建而生、随线程销毁回收线程间完全隔离无并发竞争问题。4.2.1 程序计数器JVM 最小内存单元仅存储指令指针地址唯一无 OOM 的内存区域。执行普通 Java 方法记录当前执行的字节码指令地址。执行 Native 方法计数器值为 Undefined由操作系统直接执行不受 JVM 管控。4.2.2 虚拟机栈Java 栈每个方法执行对应一个栈帧方法调用、执行、结束对应栈帧入栈、运行、出栈。每个栈帧包含四大核心组件1. 局部变量表编译期确定内存大小是固定长度数组存放方法参数、局部变量、基本数据类型、对象引用指针、返回地址。运行期间大小不可变更。2. 操作数栈执行引擎的临时计算工作台基于栈式架构运行。所有加减乘除、赋值运算均通过压栈、弹栈完成是字节码运算的核心载体。3. 动态链接栈帧持有运行时常量池引用运行期将字节码中的符号引用动态转换为方法、字段的直接内存引用支撑多态与动态绑定。4. 方法返回地址记录方法调用位置方法正常 return 退出或异常退出时恢复上层方法执行上下文继续执行业务逻辑。4.2.3 本地方法栈专门为native本地方法服务。Native 方法由 C/C 编写编译为系统底层动态库用于操作操作系统内存、硬件、实现高性能底层能力弥补 Java 底层操作短板。