跳到正文
Elaine Blog
返回

运行时数据区与 Java 内存模型不要混淆

JVM 与性能

运行时数据区回答“执行数据放在哪里”,Java 内存模型回答“多线程读写何时可见、允许怎样重排”。两者名称相似, 但解决的问题不同。把它们混在一起,会得出“对象在堆上所以线程安全”之类的错误结论。

Table of contents

Open Table of contents

JVM 规范中的运行时数据区

数据区是否线程共享保存内容常见失败
JVM 栈方法栈帧、局部变量、操作数栈StackOverflowError
PC 寄存器当前线程执行位置通常不可直接诊断
本地方法栈Native 方法执行状态实现相关
对象和数组OutOfMemoryError: Java heap space
方法区类结构、运行时常量池、方法代码实现相关

方法区是规范概念。HotSpot 自 Java 8 起主要使用本地内存中的**元空间(Metaspace)**保存类元数据,但“方法区等于 元空间”不是所有 JVM 的规范要求。

运行 jcmd <pid> VM.flagsjcmd <pid> GC.heap_info 可以观察 HotSpot 的实际配置。不要把线程转储里的栈帧数量 直接换算为堆占用;它们来自不同区域。

JMM 规定线程间关系

**JMM(Java Memory Model,Java 内存模型)**定义共享变量的可见性、原子性和有序性。关键规则是 happens-before(先行发生):若操作 A happens-before B,B 必须看到 A 的效果。

常见关系包括:

volatile 能提供可见性和一定的有序性,但 count++ 仍包含读取、计算和写入三个步骤,不会因此变成原子操作。

final class 停止信号 {
    private volatile boolean stopped;
    void stop() { stopped = true; }
    void runLoop() {
        while (!stopped) {
            Thread.onSpinWait();
        }
    }
}

这个例子只适合演示可见性。生产代码通常使用中断、锁、队列或并发工具,不应让线程持续空转。

引用在栈上不等于对象私有

局部变量中的引用位于当前栈帧,但它指向的对象可以被其他线程共享。反过来,JIT 也可能通过逃逸分析消除看似在堆上的 对象。线程安全取决于共享方式和同步关系,不取决于一句“对象在堆还是栈”。

诊断内存问题时使用 Heap Dump、NMT 和 GC 日志;诊断并发可见性时检查锁、volatile、原子类和 happens-before。 证据工具也应对应问题类型。

下一步

阅读对象创建、布局、逃逸分析与分配路径,观察一个 new 在 HotSpot 中可能经历什么。


分享这篇文章:

上一篇
类加载的双亲委派、隔离与打破方式
下一篇
对象创建、布局、逃逸分析与分配路径