JVM知识点

发布于:2025-06-22 ⋅ 阅读:(17) ⋅ 点赞:(0)

🏭 JVM 是什么?—— 一个超级工厂

想象你要运行一个 Java 程序(比如一个 .jar 文件)。你的电脑(Windows/Mac/Linux)是物理世界,它不懂 Java 程序说什么。JVM 就是建在这个物理世界上的一个虚拟工厂,专门负责执行 Java 程序。

核心作用:
让同一份 Java 程序(字节码)能在不同操作系统(Windows/Mac/Linux)上运行!
(一次编译,到处运行 —— Write Once, Run Anywhere)


🧩 JVM 的核心工作流程(工厂运作图)

编译
Java 源代码 .java
Java 字节码 .class
JVM
加载 Class
验证字节码
解释/编译执行
内存管理
垃圾回收
结果输出
🔹 1. 进货 & 质检(类加载器 - ClassLoader)
  • 任务: 把编译好的 .class 文件(字节码,相当于“零件图纸”)搬进工厂。
  • 过程:
    • 加载: 找到 .class 文件,读入二进制数据。
    • 链接:
      • 验证: 检查“图纸”是否安全、符合规范(防止恶意代码)。
      • 准备: 给类变量(static变量)在内存里划好地方,设个初始值(比如 0 或 null)。
      • 解析: 把符号引用(名字)替换成直接的内存地址引用(门牌号)。
    • 初始化: 正式执行类的静态代码块(static{})和给静态变量赋真正的值(工厂正式开始组装)。
🔹 2. 车间(运行时数据区 - Runtime Data Areas)

工厂内部有几个核心车间,存放程序运行时的东西:

  • 📚 方法区(Method Area): 存放“永久性”的东西。
    • 放什么: 加载的类信息(类名、父类、方法签名、常量池等)、static 变量、常量(final)、编译后的代码(机器指令)。像工厂的“永久仓库”和“设计图纸库”。
    • 注意: JDK 8 后,static 变量和字符串常量池移到了 堆(Heap) 里。
  • 🗑️ 堆(Heap): 最大的车间,大家共享。
    • 放什么: 所有对象实例new 出来的东西)和数组。这是垃圾回收(GC)的主战场。想象成堆满各种产品的“大仓库”。
    • 特点: 线程共享。需要垃圾回收器不断清理没用的对象腾地方。
  • 📝 栈(Stack): 每个工人(线程)有自己的小工作台。
    • 放什么: 方法执行时的信息。每调用一个方法,就压入一个栈帧(Stack Frame)。栈帧里包含:
      • 局部变量表: 存放方法里的局部变量(基本类型值、对象引用地址)。
      • 操作数栈: 进行计算的临时空间(像计算器)。
      • 动态链接: 指向方法区里该方法的实际信息。
      • 方法返回地址: 方法结束后该回到哪里。
    • 特点: 线程私有。后进先出(LIFO)。方法结束,栈帧弹出销毁。非常高效。
  • 🧮 程序计数器(Program Counter Register): 每个工人的“进度条”。
    • 作用: 记录当前线程正在执行的字节码指令的地址(如果执行的是本地方法则为空)。线程切换后回来,靠它知道上次干到哪了。
    • 特点: 线程私有。不会发生内存溢出(OutOfMemoryError)。
  • 🔧 本地方法栈(Native Method Stack): 调用“本地方法”的专用区。
    • 作用: 为用其他语言(如 C/C++)写的、通过 JNI(Java Native Interface)调用的本地方法(Native Method) 服务。
    • 特点: 线程私有。作用和结构类似 Java 栈。
🔹 3. 工人(执行引擎 - Execution Engine)

负责真正干活的:

  • 解释器(Interpreter): 拿到字节码指令,逐条解释执行(读一条图纸,做一步操作)。启动快,执行慢。
  • 即时编译器(JIT Compiler - Just-In-Time Compiler): 工厂运行一段时间后,它会把热点代码(频繁执行的代码段)整个编译成本地机器码,下次直接执行高效的机器码。执行快,但编译需要时间。HotSpot VM 的名字就源于它能找到“Hot Spots”(热点)进行编译优化。
  • 垃圾回收器(Garbage Collector - GC): 工厂里最重要的清洁工 👷♂️。自动回收堆里不再被任何引用指向的对象,释放内存空间。这是 Java 自动内存管理的核心,避免了 C/C++ 需要手动 free/delete 的麻烦和风险。
🔹 4. 本地接口(JNI - Java Native Interface)

工厂和外部世界(本地操作系统、其他语言库)沟通的桥梁。让 Java 程序可以调用 C/C++ 等写的库,或者被其他程序调用。


🗑️ 垃圾回收(GC)—— 清洁工的工作(重点!)

这是 JVM 最核心的特性之一:自动内存管理

  1. 目标: 回收堆(Heap)里已经“死掉”的对象(没有任何引用指向它)。
  2. 核心思想:分代收集: 根据对象“年龄”(存活时间)把堆分成不同区域,不同区域用不同回收策略,提高效率。
    • 新生代(Young Generation):new 出来的对象大部分很快死去。
      • Eden 区: 对象出生地。新对象都放这里。
      • Survivor 区(S0, S1): 经历过一次 GC 还活着的对象会移到 Survivor 区(两个区轮流使用)。对象在 Survivor 区之间“熬过”几次 GC,年龄就增加。
    • 老年代(Old Generation/Tenured): 在新生代“熬”过一定次数(年龄阈值,默认15)GC 还活着的对象,晋升到这里。这里对象寿命长,GC 频率低。
    • 永久代(PermGen) / 元空间(Metaspace)(JDK8+): 存放类元信息等。不属于堆。JDK8 后用元空间(使用本地内存)替代永久代,减少 OOM 风险。
  3. GC 过程(简化版):
    • Minor GC / Young GC: 只清理新生代(Eden + Survivor)。
      • 新对象进 Eden。
      • Eden 满 -> 触发 Minor GC。
      • 活着的对象复制到空的 Survivor 区(如 S0),年龄+1,清空 Eden 和另一个 Survivor(S1)。
      • 下次 Minor GC,活着的对象(包括 S0 里的)复制到 S1,年龄再+1。如此反复。
      • 年龄达到阈值 -> 晋升到老年代。
    • Major GC / Full GC: 清理整个堆(新生代 + 老年代 + 方法区/元空间)。通常比 Minor GC 慢很多倍!要尽量避免频繁 Full GC。
  4. 常用 GC 算法: 标记-清除、标记-整理、复制算法。不同区域的 GC 器组合使用不同算法(如新生代用复制算法)。

🔄 一、GC 算法详解(垃圾回收的核心手段)

垃圾回收的目标是高效清理堆内存中的无效对象,主流算法分三类:

1. 标记-清除(Mark-Sweep)
  • 过程
    1. 标记:从 GC Roots(如静态变量、栈中局部变量)出发,标记所有可达对象。
    2. 清除:遍历堆内存,回收未被标记的对象。
  • 缺点
    • 内存碎片:产生大量不连续碎片,导致大对象分配失败。
    • 效率问题:标记和清除过程效率较低。
2. 复制算法(Copying)
  • 过程
    • 将内存分为 Eden区 + 2个Survivor区(S0, S1)
    • 对象首先分配在 Eden 区,Minor GC 时将 存活对象复制到空的 Survivor 区,清空原区域。
    • 对象在 Survivor 区间多次复制后(默认15次),晋升老年代。
  • 优点
    • 无内存碎片,分配高效。
  • 缺点
    • 浪费空间:一半内存(Survivor)始终闲置。
    • 适用场景新生代(对象存活率低)。
3. 标记-整理(Mark-Compact)
  • 过程
    1. 标记:同标记-清除。
    2. 整理:将所有存活对象向内存一端移动,清理边界外内存。
  • 优点
    • 无内存碎片
  • 缺点
    • 移动对象成本高。
    • 适用场景老年代(对象存活率高)。

⚙️ 二、JVM 调优实战指南

调优核心目标:减少 Full GC 频率与耗时,避免 OOM

1. 关键参数解析
参数 作用 示例值
-Xms / -Xmx 堆初始大小 / 最大大小 -Xms4g -Xmx4g
-Xmn 新生代大小 -Xmn2g
-XX:MetaspaceSize 元空间初始大小(JDK8+) -XX:MetaspaceSize=256m
-XX:MaxMetaspaceSize 元空间最大大小 -XX:MaxMetaspaceSize=512m
-XX:SurvivorRatio Eden 与 Survivor 区的比例 -XX:SurvivorRatio=8 (Eden:S0:S1=8:1:1)
-XX:MaxTenuringThreshold 对象晋升老年代的年龄阈值 -XX:MaxTenuringThreshold=15
2. GC 日志分析(调优第一步!)

启用日志参数:

-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:gc.log

关键指标

  • Young GC 时间:通常应 < 50ms
  • Full GC 频率:一天超过1次需警惕
  • Full GC 时间:超过1秒是严重问题
3. 调优场景与策略
问题现象 可能原因 解决方案
频繁 Full GC 老年代空间不足 增大 -Xmx,减少大对象分配
Young GC 时间长 Survivor 区过小 增大 -Xmn 或调整 -XX:SurvivorRatio
Metaspace OOM 类加载过多(如动态代理) 增大 -XX:MaxMetaspaceSize
GC 停顿时间过长 老年代使用 CMS/G1 失败 切换为 G1 并优化 -XX:MaxGCPauseMillis
4. 选择垃圾回收器
回收器 特点 适用场景
Serial 单线程,简单高效 客户端小应用
Parallel 多线程,吞吐量优先 后台计算型应用
CMS 并发标记清除,低停顿 Web 服务(JDK8 主流)
G1 分Region收集,可预测停顿 大内存、低延迟(JDK9+默认)
ZGC 亚毫秒级停顿(JDK15+) 超低延迟金融系统

G1 调优示例

-XX:+UseG1GC 
-XX:MaxGCPauseMillis=200  # 目标停顿时间
-XX:G1HeapRegionSize=4m   # Region大小(建议不设)

🧩 三、类加载机制深度解析

1. 类加载过程
Loading
Linking
Verification
Preparation
Resolution
Initialization
  1. 加载(Loading)

    • 通过类名获取二进制字节流。
    • 生成 Class 对象(方法区)。
  2. 链接(Linking)

    • 验证:检查字节码合法性(魔数、语法等)。
    • 准备:为静态变量分配内存并赋零值(如 int=0boolean=false)。
    • 解析:将符号引用转为直接引用(延迟到初始化前)。
  3. 初始化(Initialization)

    • 执行静态代码块(static{})和静态变量赋值。
    • 触发条件new、访问静态变量/方法、反射等。
2. 类加载器(ClassLoader)层次
自定义类加载器
AppClassLoader
ExtClassLoader
BootstrapClassLoader
  • Bootstrap ClassLoader(C++实现)
    • 加载 JAVA_HOME/lib 的核心类(如 java.lang.*)。
  • Extension ClassLoader
    • 加载 JAVA_HOME/lib/ext 的扩展类。
  • Application ClassLoader
    • 加载 classpath 下的应用类。
  • Custom ClassLoader
    • 用户自定义(如 Tomcat 的 WebappClassLoader)。
3. 双亲委派模型(核心安全机制)
  • 工作流程
    1. 收到加载请求时,先委托父加载器处理。
    2. 父加载器无法完成时,才自己加载。
  • 目的
    • 避免核心类被篡改(如自定义 java.lang.String 无效)。
    • 保证类全局唯一性。
4. 打破双亲委派的场景
  • SPI 机制(如 JDBC 驱动加载)
    • ServiceLoader 用线程上下文类加载器(ThreadContextClassLoader)加载实现类。
  • 热部署(如 Tomcat)
    • 每个 Web 应用用独立的 WebappClassLoader 优先加载自身类。

🔧 四、实战工具链

  1. 监控工具
    • jps:查看 Java 进程
    • jstat -gc <pid>:实时 GC 统计
    • jmap -heap <pid>:堆内存分析
  2. 故障诊断
    • jstack <pid>:抓取线程栈(查死锁)
    • MAT / VisualVM:分析堆转储(-XX:+HeapDumpOnOutOfMemoryError

📌 总结关键点(记住这些就懂了!)

  1. 跨平台核心: JVM 让 Java 字节码 (.class) 能在任何装了 JVM 的机器上运行。
  2. 类加载: 加载、验证、准备、解析、初始化 .class 文件。
  3. 内存管理(核心!):
    • 方法区: 类信息、常量池(JDK8 后部分在堆)。
    • 堆: 所有对象和数组,GC 主战场(分新生代、老年代)。
    • 栈: 线程私有,放方法调用的栈帧(局部变量、操作数栈等)。
    • 程序计数器: 线程私有,指向当前执行的指令地址。
    • 本地方法栈: 服务于本地方法。
  4. 执行引擎: 解释器(启动快)、JIT 编译器(运行快)、垃圾回收器(自动清理堆内存)。
  5. 自动垃圾回收(GC): JVM 最大福利之一! 基于分代收集(新生代、老年代),自动回收无用对象内存。程序员基本不用操心内存释放(但也需注意避免内存泄漏)。

💡 简单来说

JVM 就是一个建在你电脑操作系统上的虚拟工厂。它接收 Java 编译好的“通用图纸”(字节码),在自己的车间(内存区域)里,由工人(执行引擎)按照图纸生产(创建对象)、计算(执行指令),并有专门的清洁工(GC)自动打扫废弃产品(回收内存),最终让 Java 程序在你的电脑上顺利运行起来,并且不管电脑是 Windows、Mac 还是 Linux!


网站公告

今日签到

点亮在社区的每一天
去签到