一、整体概览

JVM(Java Virtual Machine)在运行时会把内存划分成若干区域,用于存放不同类型的数据和执行信息。JDK 8 之后的 JVM 内存结构主要分为以下部分:

  • 线程私有区域

    • 程序计数器 (Program Counter Register)

    • Java 虚拟机栈 (JVM Stack)

    • 本地方法栈 (Native Method Stack)

  • 线程共享区域

    • 堆 (Heap)

    • 方法区 (Method Area,JDK 8 以后由元空间 Metaspace 替代)

  • 直接内存 (Direct Memory)

    • 不属于 JVM 规范中的运行时数据区,但常在 NIO 中用到。


二、各个区域详解

1. 程序计数器(PC 寄存器)

  • 作用:记录当前线程执行的字节码指令的地址。

  • 特点

    • 每个线程独有,线程切换时能恢复到正确执行位置。

    • 不会出现 OOM


2. Java 虚拟机栈(JVM Stack)

  • 作用:描述方法执行的内存模型。

  • 栈帧(Stack Frame):每个方法执行时会创建一个栈帧,存放:

    • 局部变量表(Local Variable Table)

    • 操作数栈(Operand Stack)

    • 动态链接(指向运行时常量池的方法引用)

    • 方法返回地址

  • 异常

    • 栈深度超过限制:StackOverflowError

    • 内存不足无法扩展:OutOfMemoryError


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

  • 作用:为虚拟机调用 本地方法(Native,通常是 C/C++ 代码) 服务。

  • 异常:也可能抛出 StackOverflowErrorOutOfMemoryError


4. 堆(Heap)

  • 作用:存放对象实例和数组,是 GC 管理的主要区域

  • 特点

    • 线程共享

    • JVM 启动时创建

    • 可以物理上不连续,但逻辑上连续

  • 内存溢出:对象过多且无法回收时,抛出 OutOfMemoryError: Java heap space

  • 分代模型(常见)

    • 新生代(Young Generation):Eden + Survivor(From/To)

    • 老年代(Old Generation)

    • (JDK 8 前)永久代(PermGen,存放类元数据等,已被移除)


5. 方法区(Method Area) / 元空间(Metaspace)

  • 作用:存放类的结构信息,例如:

    • 类的元数据(类名、修饰符、方法、字段等)

    • 常量池(运行时常量池)

    • 静态变量

    • JIT 编译后的代码

  • JDK 8 前:方法区在堆中实现,称为 永久代(PermGen)

  • JDK 8 后:使用本地内存(Native Memory)实现,叫 元空间(Metaspace)

  • 异常OutOfMemoryError: Metaspace


6. 运行时常量池(Runtime Constant Pool)

  • 作用:存放编译期生成的各种字面量和符号引用(类名、方法名、字段名、字符串字面量等)。

  • 位置:属于方法区的一部分。

  • 特点:可以动态扩展,比如 String.intern() 会往常量池添加字符串。

  • 异常OutOfMemoryError: PermGen space(JDK 7 及以前)或 OutOfMemoryError: Metaspace(JDK 8+)。


7. 直接内存(Direct Memory)

  • 作用:堆外内存,主要由 NIO 分配,性能更高(避免 Java 堆与 Native 堆的数据拷贝)。

  • 分配方式ByteBuffer.allocateDirect()

  • 异常:超出限制会抛出 OutOfMemoryError: Direct buffer memory


三、总结口诀

  • 线程私有:程序计数器、虚拟机栈、本地方法栈

  • 线程共享:堆、方法区

  • 非规范但常用:直接内存

JVM 垃圾收集算法

一、常见的 GC 算法

1. 标记-清除(Mark-Sweep)

  • 过程

    1. 标记:从 GC Roots 出发,标记所有可达对象。

    2. 清除:回收未被标记的对象。

  • 优点:实现简单。

  • 缺点

    • 内存碎片化(清理后留下很多不连续的内存块)。

    • 分配大对象时可能找不到足够连续内存 → OOM。


2. 复制(Copying)

  • 过程

    1. 将内存划分为两块(From / To)。

    2. 每次只使用一块(From)。

    3. GC 时,把存活对象复制到另一块(To),再清空原来的那块。

  • 优点

    • 没有碎片问题。

    • 分配效率高(指针碰撞)。

  • 缺点

    • 浪费一半内存空间。

    • 如果存活对象很多,复制开销大。

  • 适用场景:新生代(大多数对象很快死亡,存活对象少,复制成本低)。


3. 标记-整理(Mark-Compact)

  • 过程

    1. 标记所有存活对象。

    2. 将存活对象移动到内存一端,保持连续。

    3. 清理边界外的内存。

  • 优点:解决了内存碎片问题。

  • 缺点:对象移动,性能开销较大。

  • 适用场景:老年代(存活对象较多,复制算法不适合)。


4. 分代收集(Generational Collection)

  • 思想:根据对象生命周期长短,把堆分为不同区域,用不同的算法:

    • 新生代:对象朝生夕死,采用 复制算法

    • 老年代:对象存活率高,采用 标记-清除标记-整理

  • 优点:综合利用不同算法的长处,是目前商用 JVM 的主流。


二、特点对比表

算法

是否有碎片

是否移动对象

空间利用率

适用场景

标记-清除

老年代

复制

低(浪费一半)

新生代

标记-整理

老年代

分代收集

结合优缺点

新生代复制、老年代标记类算法

高效

JVM 默认


三、扩展(回答加分点)

  • 可达性分析(GC Roots):JVM 判断对象是否存活的主流方法(栈引用、本地方法栈、静态变量、常量池引用等)。

  • 现代垃圾收集器(基于这些算法实现):

    • Serial、Parallel Scavenge、CMS、G1、ZGC、Shenandoah。

    • 面试时可以顺带提到 CMS(并发标记清除,低停顿)G1(分区化、可预测停顿时间),加分。