JVM 类加载机制是将 class 文件加载到内存、验证、解析并初始化的过程,分为加载、验证、准备、解析、初始化五个固定阶段。核心是把字节码转为可用的 Java 类,全程懒加载,用到时才加载。类加载器分四层层次结构:最顶层是启动类加载器,加载 JDK 核心类库;下层扩展类加载器,加载扩展目录的类;应用程序类加载器,加载我们自己写的业务代码;最下层是自定义类加载器,可定制加载规则。四层加载器是逻辑父子关系,遵循固定的委托规则,保证类加载的安全与稳定。
双亲委派模型是类加载的核心规则,简单说就是向上委托,向下加载。当类加载器收到加载请求时,不会自己先加载,而是一层层交给父类加载器,直到顶层启动类加载器。如果父类能加载,就直接完成;父类无法加载时,才由子类加载器自己处理。这个模型能防止核心类被恶意篡改,比如自定义 String 类无法替代 JDK 原生类,同时保证同一个类只被加载一次,避免重复创建 Class 对象。它是 JVM 类加载安全的基石,也是类加载器最核心的工作机制。
Java 类的初始化有严格固定顺序,遵循先静态、后成员,先父类、后子类原则。首先执行父类静态代码块、静态变量,再执行子类静态代码块、静态变量,静态内容只在类加载时执行一次。接着初始化父类,先执行普通成员变量、构造代码块,最后执行父类构造方法。然后初始化子类,同样先普通成员、构造代码块,再执行子类构造方法。静态资源属于类本身,优先于对象实例;父类初始化完成后,子类才能初始化,保证继承关系的正确性。
JVM 内存分线程私有和线程共享两大区域,还有直接内存。线程私有区域随线程创建销毁,互不干扰:程序计数器记录指令执行位置;虚拟机栈存储方法栈帧、局部变量;本地方法栈服务 Native 方法。线程共享区域是 JVM 核心内存:堆存储所有对象实例、数组,是 GC 主要区域;方法区存储类元信息、常量、静态变量。直接内存不属于运行时数据区,是堆外内存,用于 NIO 提升 IO 效率。各区域分工明确,保障程序稳定运行。
元空间是 JDK1.8 后用来替代永久代的区域,核心区别很大。永久代属于堆内存,大小固定,很容易触发 OOM;元空间使用本地物理内存,默认无上限,可手动限制,大幅降低溢出风险。存储内容上,永久代存类元数据、字符串常量池、静态变量;元空间只存类元数据,常量池和静态变量移到了堆中。永久代受 JVM 堆内存限制,元空间只受物理内存限制,回收效率更高。永久代在 1.8 被彻底移除,元空间解决了永久代内存溢出的痛点。
方法区是 JVM 线程共享区域,核心作用是存储类的元数据信息,比如类结构、方法代码、字段信息、静态变量、运行时常量池,是类加载后的数据载体。它和堆的区别很明显:堆存储对象实例和数组,是 GC 最频繁的区域;方法区存储类的结构信息,GC 频率低。堆内存可动态扩容,方法区在 1.8 后用元空间(本地内存)。堆存放运行时创建的对象,方法区存放类的模板信息,两者都是共享区域,但存储内容、GC 策略、内存用途完全不同。
JVM 垃圾回收机制是自动回收堆中无用对象、释放内存的机制,无需手动管理内存,避免内存泄漏。核心有四种回收算法:标记 - 清除算法,先标记存活对象,再清除垃圾,简单但产生内存碎片;标记 - 整理算法,标记后将存活对象移到一端,清除碎片,适合老年代;复制算法,将内存分两半,复制存活对象到另一半,清空原区域,效率高,适合年轻代;分代收集算法是 JVM 默认方案,结合前三种,年轻代用复制,老年代用标记清除 / 整理,适配不同对象生命周期。
垃圾收集器分年轻代、老年代和全区域收集器,适配不同场景。年轻代:Serial 单线程收集,适合小内存;ParNew 多线程,配合 CMS 使用;Parallel Scavenge 注重吞吐量。老年代:Serial Old 单线程标记整理;CMS 并发标记清除,停顿时间短,适合响应优先场景;Parallel Old 多线程吞吐量优先。全区域:G1 兼顾吞吐量和响应,分区回收;ZGC/Shenandoah 超低停顿,适合大内存。选择依据是业务需求,追求响应用 CMS/G1,追求吞吐量用 Parallel 系列。
垃圾回收处理对象循环依赖,核心是抛弃引用计数法,使用可达性分析算法。引用计数法无法处理循环依赖,A 引用 B、B 引用 A,计数器永远不为 0,导致无法回收。JVM 采用可达性分析,从 GC Roots(线程栈、静态变量等核心引用)出发,遍历引用链。即使两个对象循环引用,只要和 GC Roots 没有连接,就会被判定为不可达对象,直接回收。这种方式完美解决循环依赖问题,是 JVM 判断对象死亡的标准方案,安全可靠。
安全点和安全区域是 JVM 为了安全执行 GC 设计的机制,防止 GC 时线程修改对象引用。安全点是线程执行中的特定位置,比如方法调用、循环结束、异常抛出,GC 会等待所有线程跑到安全点再暂停,保证引用状态稳定。安全区域针对休眠 / 阻塞线程,这些线程无法到达安全点,进入安全区域后,GC 可直接执行,无需等待。线程唤醒后会检查 GC 是否完成,再继续运行。两者互补,确保 GC 全程安全,不出现数据错乱。
finalize()是 Object 类的方法,设计初衷是在对象被 GC 回收前,执行资源释放操作,比如关闭文件、释放连接。但它存在严重缺陷:执行时机完全不确定,JVM 不保证及时调用;方法内可以给对象重新赋值,让对象“复活”,干扰 GC;抛出异常不会被捕获,容易导致内存泄漏;执行会占用 GC 线程,严重降低回收效率。因为这些致命问题,finalize() 在 JDK9 中被标记为废弃,现在用 try-with-resources 语法释放资源,更安全高效。
JVM 调优核心是减少 GC 停顿、避免 OOM、提升吞吐量。实践步骤:先监控分析问题,再调整内存参数(堆、栈、元空间大小),选择合适 GC 收集器,优化代码减少大对象和内存泄漏。常用监控工具:命令行工具有 jps 查进程、jstat 监控 GC、jmap 导出堆快照、jstack 查线程死锁;图形化工具有 JVisualVM、JProfiler,直观查看内存和 GC;阿里 Arthas 适合生产环境在线排查,无需重启。调优核心是按需配置,没有固定参数,贴合业务场景才最优。
JIT 即时编译器是 JVM 的核心优化,解决 Java 解释执行效率低的问题。JVM 默认采用解释 + 编译混合模式:启动时用解释器快速运行,运行中统计热点代码(频繁执行的方法、循环),JIT 将其编译为机器码,缓存后直接执行,速度大幅提升。JIT 分 C1(轻量编译,启动快)和 C2(深度优化,运行快)。核心优化包括:逃逸分析、标量替换、栈上分配、锁消除、常量折叠等,减少对象创建、降低锁竞争,让 Java 程序运行效率接近原生代码,是 JVM 高性能的关键。