1. JMM 是什么?

JMM 是 Java 虚拟机规范的一部分,它定义了 Java 程序中多线程读写共享内存时的行为规范
它主要解决三个问题:

  1. 可见性

    • 一个线程对共享变量的修改,什么时候对其他线程可见?

  2. 原子性

    • 如何保证一组操作不会被线程切换打断?

  3. 有序性

    • 程序代码的执行顺序在多线程下还能不能保持?


2. JMM 的核心抽象

JMM 把内存划分为两类:

  • 主内存(Main Memory)
    存放所有线程共享的变量(堆里的对象实例字段、静态字段、数组元素)。

  • 工作内存(Working Memory)
    每个线程私有,类似于寄存器/CPU 缓存,保存主内存中变量的副本。

👉 线程对变量的所有操作(读/写)必须在工作内存中进行,不能直接操作主内存
👉 不同线程之间变量值的传递,必须通过主内存来完成。


3. JMM 的 8 大操作

为了规范线程与主内存的交互,JMM 定义了 8 种原子操作:

  1. lock(锁定)

  2. unlock(解锁)

  3. read(从主内存读)

  4. load(写入工作内存副本)

  5. use(在执行引擎中使用变量值)

  6. assign(在执行引擎中赋值)

  7. store(把工作内存的值写回主内存)

  8. write(更新到主内存变量)

👉 这些操作保证了 JMM 在多线程下的一致性和安全性。


4. JMM 解决的三个关键问题

  1. 可见性

    • 一个线程修改了共享变量,其他线程能否马上看到?

    • volatilesynchronizedLock 保证。

  2. 有序性

    • 代码执行顺序是否和写的顺序一致?

    • 编译器和 CPU 会 指令重排序,JMM 通过 happens-before 规则 来约束。

  3. 原子性

    • 基本读写是原子的,但复合操作(如 i++)不是。

    • 通过 synchronizedLock 或原子类保证。


5. happens-before 原则(JMM 的核心规则)

JMM 不要求所有操作完全按照代码顺序执行,而是通过 happens-before 关系 来保证正确性:

  • 程序次序规则:单线程内,代码按顺序执行。

  • 锁规则:unlock 操作 happens-before 之后的 lock。

  • volatile 规则:对一个 volatile 变量的写 happens-before 之后的读。

  • 传递性:A happens-before B,B happens-before C,则 A happens-before C。


6. 总结

  • JMM 是一种抽象规范,不是具体实现。

  • 它定义了线程和内存之间的交互规则,解决 可见性、原子性、有序性 问题。

  • 核心机制:主内存 + 工作内存模型8 大操作happens-before 规则

  • 实际保证手段:volatilesynchronizedfinalLock

什么是逃逸分析

好问题 👍,逃逸分析(Escape Analysis) 是 JVM 中 即时编译器(JIT) 的一种优化技术,主要用来分析 对象的作用域,判断一个对象是否会“逃逸”出方法或线程。


1. 什么是逃逸?

对象的引用是否会被方法或线程之外的地方使用

  • 没有逃逸:对象只在当前方法里使用,没有被返回,也没有传递到方法外部。

  • 方法逃逸:对象被返回或传递给外部方法。

  • 线程逃逸:对象被其他线程访问,例如赋值给了全局变量、类静态变量。


2. 逃逸分析能带来什么优化?

JVM JIT 编译器通过逃逸分析,可以判断对象的生命周期,从而进行以下优化:

(1) 栈上分配(Stack Allocation)

正常情况下,对象是在 堆上分配的,方法结束后等待 GC 回收。
如果通过逃逸分析确定一个对象 不会逃出方法范围,JVM 就可以把它分配到 栈帧里,方法结束时栈帧销毁,对象也随之销毁,减少了 GC 压力。

(2) 标量替换(Scalar Replacement)

如果一个对象不会被外部访问,JVM 可以把这个对象 拆散成若干个局部变量 来存储。
例如:

class Point {
    int x;
    int y;
}

public void test() {
    Point p = new Point(); // 不逃逸
    p.x = 1;
    p.y = 2;
    System.out.println(p.x + p.y);
}

👉 经过标量替换后,JVM 可能直接用两个局部变量 int x=1; int y=2;,甚至不真正创建 Point 对象。

(3) 消除同步(Lock Elimination)

如果通过逃逸分析发现一个对象不会被其他线程访问,那么即使你写了 synchronized,JVM 也可以 去掉锁操作,提升性能。


3. 举个例子

public class EscapeDemo {
    public String createStringBuffer(String s1, String s2) {
        StringBuffer sb = new StringBuffer(); // sb 是否逃逸?
        sb.append(s1);
        sb.append(s2);
        return sb.toString();  // 只返回结果字符串,不返回 sb 本身
    }
}
  • sb 没有逃逸(只在方法内部使用)。

  • toString() 返回的是一个新的 String 对象。

  • JVM 可能会优化成 栈上分配标量替换

但如果改成:

public StringBuffer createBuffer(String s1, String s2) {
    StringBuffer sb = new StringBuffer();
    sb.append(s1);
    sb.append(s2);
    return sb;  // 这里 sb 对象逃逸出方法
}

👉 sb 对象返回给外部,必须分配在堆上。


4. 如何验证逃逸分析?

JVM 参数:

  • -XX:+DoEscapeAnalysis(开启逃逸分析,JDK 8 默认开启)

  • -XX:+PrintEscapeAnalysis(打印逃逸分析结果)

  • -XX:+EliminateAllocations(标量替换)


5. 总结

  • 逃逸分析:JVM 分析对象是否会逃出方法或线程的过程。

  • 优化手段

    1. 栈上分配 → 减少 GC 压力

    2. 标量替换 → 拆分对象,甚至不创建对象

    3. 锁消除 → 去掉无必要的 synchronized