# JVM内存规范下篇

作者:Ethan.Yang
博客:https://blog.ethanyang.cn (opens new window)
相关源码参考: https://github.com/YangYingmeng/_014JVM (opens new window)


# 一、运行时数据区图解

JVM 的内存整体分为两大区域:

  • 非堆区(Non-Heap):方法区、代码缓存、本地方法栈、线程栈等;
  • 堆区(Heap):用于存放对象实例,是垃圾回收器的主要工作区域。

堆内部分为:

  • 年轻代(Young Generation):用于存放新创建的对象;
  • 老年代(Old Generation):用于存放生命周期较长或经历多次回收仍存活的对象。

年轻代又细分为:

  • Eden 区
  • 两个 Survivor 区(S0 / S1)

Eden、S0、S1 的比例通常为 8 : 1 : 1

说明:

  • 每个线程独享虚拟机栈和程序计数器。
  • 堆和方法区为所有线程共享。
  • 对象创建、指向关系、以及生命周期的变化都发生在堆中。

# 二、对象创建内存分配过程

  • 当在 Java 中通过 new 关键字创建对象时:

    1. 类加载检查

      若类尚未加载,会先进行加载、验证、准备、初始化等步骤。

    2. 分配内存空间

      JVM 会根据对象大小在堆上分配空间,默认优先在 Eden 区

    3. 初始化对象头

      JVM 会在对象头写入:

      • Mark Word(哈希码、GC 年龄、锁状态等)
      • Klass Pointer(指向类元数据的指针)
    4. 执行构造方法

      按照字段定义顺序初始化成员变量,然后执行 <init> 构造函数。

    5. 返回引用

      最终返回引用地址,存储在栈帧的局部变量表中。

# 三、对象的生命周期

# 创建阶段

JVM 为对象分配内存空间,并从超类到子类依次完成

  • 静态成员初始化
  • 成员变量按顺序初始化
  • 调用构造方法(先递归调用父类构造方法,再调用子类构造方法)

对象构造完成后,被变量持有,即进入应用阶段。

# 应用阶段

对象至少被一个强引用持有,程序可以正常访问并操作该对象。

# 不可见阶段

超出作用域或引用被修改,但仍存在潜在引用路径。

# 不可达阶段

对象无法被任何强引用访问,可进入 GC 待回收队列。

# 终结阶段

如重写了 finalize(),GC 会先调用一次;若执行后仍不可达,则进入回收阶段。

# 内存释放

GC 回收对象占用空间,等待新的内存分配。

⚠️ 建议避免使用 finalize():它会导致 GC 多一次标记过程,并可能引发对象“复活”问题。


# 四、线程与对象的内存交互

  • 每个线程拥有独立的虚拟机栈(局部变量表、操作数栈等);
  • 所有线程共享堆和方法区;
  • 当方法中定义对象时,局部变量表中存储的是“堆中对象的引用”;
  • 多线程访问共享对象时,通过堆中的实例完成交互,而不是栈之间直接通信。