从零起步学习JVM|| 第二章:JVM基本组成及JVM内存区域详解

0 阅读6分钟

 Q1:JVM由哪些部分组成?

A1:

1. 类加载器子系统 (Class Loader Subsystem)

负责加载、链接和初始化类。它将 .class 文件加载到内存中,并生成对应的 java.lang.Class 对象。

  • 功能

    • 加载 (Loading) :查找并读取类的二进制数据。
    • 链接 (Linking) :验证、准备、解析(将符号引用替换为直接引用)。
    • 初始化 (Initialization) :执行类构造器 <clinit>() 方法,给静态变量赋初值。
  • 分类(双亲委派模型):

    • 启动类加载器 (Bootstrap ClassLoader) :加载核心类库(rt.jar 等),由 C++ 实现。
    • 平台类加载器 (Platform ClassLoader) :Java 9 之前称为扩展类加载器,加载扩展类库。
    • 应用类加载器 (Application ClassLoader) :加载用户类路径(Classpath)下的类。

2. 运行时数据区 (Runtime Data Area)

这是 JVM 的内存模型,是 JVM 规范中最核心的部分。它分为线程私有线程共享两大部分:

线程私有(随线程生灭)

  1. 程序计数器 (Program Counter Register)

    • 记录当前线程所执行的字节码的行号指示器。
    • 是唯一一个在 JVM 规范中没有规定任何 OutOfMemoryError 情况的区域。
  2. Java 虚拟机栈 (Java Virtual Machine Stack)

    • 描述 Java 方法执行的内存模型。
    • 每个方法执行时会创建一个栈帧 (Stack Frame) ,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
    • 可能抛出 StackOverflowError(栈深度过深)或 OutOfMemoryError(栈扩展失败)。
  3. 本地方法栈 (Native Method Stack)

    • 与虚拟机栈作用相似,但服务于 Native 方法(通常由 C/C++ 编写)。

线程共享(随 JVM 生灭)

  1. 堆 (Heap)

    • 最大的一块内存区域
    • 存放对象实例数组
    • 垃圾收集器 (Garbage Collector) 管理的主要区域。
    • 可能抛出 OutOfMemoryError
  2. 方法区 (Method Area) / 元空间 (Metaspace)

    • 存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码缓存等。
    • 注意:在 Java 8 及之后,方法区的实现由“永久代 (PermGen)"改为“元空间 (Metaspace)",直接使用本地内存,不再受 JVM 堆内存限制(但受物理内存限制)。

3. 执行引擎 (Execution Engine)

负责执行字节码,是 JVM 的核心。

  • 解释器 (Interpreter)

    • 逐行读取字节码并立即执行。启动速度快,但运行效率相对较低。
  • 即时编译器 (JIT Compiler, Just-In-Time)

    • 将热点代码(频繁执行的代码)编译成本地机器码,并缓存起来。
    • 下次执行时直接运行机器码,大幅提升运行效率。
    • HotSpot 虚拟机中包含 C1(客户端编译器)和 C2(服务器端编译器)。
  • 垃圾回收器 (Garbage Collector)

    • 虽然 GC 主要作用于堆内存,但在架构上常被视为执行引擎的一部分或独立模块。
    • 负责自动回收不再使用的对象,释放内存。

4. 本地方法接口 (Native Interface)

  • JNI (Java Native Interface)
  • 它的作用是融合不同的编程语言为 Java 所用。
  • 当 Java 代码需要调用非 Java 语言(如 C、C++)编写的方法时,通过 JNI 进行交互。

Q2:JVM内存区域是什么样的?

A2:

下图来自Java内存区域详解(重点) | JavaGuide

一、线程私有区域(随线程创建/销毁)

1. 程序计数器(Program Counter Register)

  • 作用:记录当前线程正在执行的字节码指令地址(行号指示器)。

  • 特点

    • 唯一不会发生 OutOfMemoryError 的区域。
    • 线程切换时,PC 计数器会保存当前执行位置,恢复时继续执行。
    • 若执行的是 Native 方法,计数器值为空(Undefined)。

2. Java 虚拟机栈(Java Virtual Machine Stack)

  • 作用:描述 Java 方法执行的内存模型,每个方法调用会创建一个栈帧(Stack Frame)

  • 栈帧包含

    • 局部变量表(Local Variables)
    • 操作数栈(Operand Stack)
    • 动态链接(Dynamic Linking)
    • 方法返回地址(Return Address)
  • 异常

    • StackOverflowError:线程请求栈深度超过虚拟机允许的最大深度(如递归过深)。
    • OutOfMemoryError:栈可动态扩展时,扩展失败。

3. 本地方法栈(Native Method Stack)

  • 作用:为 Native 方法(C/C++ 编写)服务,与虚拟机栈功能类似。
  • 特点:具体实现由虚拟机自行决定,HotSpot 将其与 Java 虚拟机栈合二为一。

二、线程共享区域(随 JVM 启动/销毁)

4. 堆(Heap)

  • 作用最大的内存区域,存放对象实例数组

  • 特点

    • 垃圾收集器(GC)管理的主要区域,几乎所有对象都在堆上分配。

    • 可细分为 新生代(Young Generation)老年代(Old Generation) (HotSpot 实现):

      • 新生代:Eden 区 + Survivor 区(From/To)
      • 老年代:存放长期存活对象
    • 可通过 -Xms(初始堆大小)和 -Xmx(最大堆大小)调整。

  • 异常OutOfMemoryError: Java heap space(堆内存不足)。

5. 方法区(Method Area)

  • 作用:存储类元数据,包括:

    • 类信息(字段、方法、接口等)
    • 运行时常量池(Runtime Constant Pool)
    • 静态变量
    • JIT 编译后的代码缓存
  • 重要演变

    • Java 7 及之前:HotSpot 使用 永久代(PermGen) 实现方法区,位于 JVM 堆内。

    • Java 8 及之后:永久代被移除,改用 元空间(Metaspace) ,使用本地内存(Native Memory) ,不再受 -Xmx 限制。

      • 元空间大小默认仅受系统物理内存限制,可通过 -XX:MaxMetaspaceSize 限制。
      • 类元数据的垃圾回收更高效,避免了永久代常见的 OutOfMemoryError: PermGen space 问题。
  • 异常OutOfMemoryError: Metaspace(元空间耗尽)。

💡 运行时常量池 是方法区的一部分,用于存放编译期生成的字面量和符号引用,在类加载后存入。例如 String s = "hello" 中的 "hello" 会进入常量池。

Q3:Java堆空间的基本结构是什么样子的?

A3:

下图来自JVM垃圾回收详解(重点) | JavaGuide

新生代(Young Generation)

  • Eden 区:新对象优先分配于此(TLAB 优化下线程本地分配)。

  • Survivor 区(S0/S1):

    • Minor GC 时,Eden + From Survivor 中存活对象复制到 To Survivor
    • 每次 GC 后 S0/S1 角色互换(“复制算法”核心)。
    • 对象年龄 +1,达到阈值(-XX:MaxTenuringThreshold,默认15)晋升老年代。
  • 特点:高频 Minor GC(Stop-The-World 时间短),回收“朝生夕死”对象。

 老年代(Old/Tenured Generation)

  • 存放:

    • 经多次 Minor GC 仍存活的对象
    • 大对象(-XX:PretenureSizeThreshold 直接分配至此)
    • Survivor 空间不足时的担保晋升对象
  • GC 行为:触发 Major GC / Full GC(耗时长,需避免频繁发生)。

Q4:Java虚拟机栈和栈帧是什么?

A3: