1. JMM 是什么?
JMM 是 Java 虚拟机规范的一部分,它定义了 Java 程序中多线程读写共享内存时的行为规范。
它主要解决三个问题:
可见性
一个线程对共享变量的修改,什么时候对其他线程可见?
原子性
如何保证一组操作不会被线程切换打断?
有序性
程序代码的执行顺序在多线程下还能不能保持?
2. JMM 的核心抽象
JMM 把内存划分为两类:
主内存(Main Memory)
存放所有线程共享的变量(堆里的对象实例字段、静态字段、数组元素)。工作内存(Working Memory)
每个线程私有,类似于寄存器/CPU 缓存,保存主内存中变量的副本。
👉 线程对变量的所有操作(读/写)必须在工作内存中进行,不能直接操作主内存。
👉 不同线程之间变量值的传递,必须通过主内存来完成。
3. JMM 的 8 大操作
为了规范线程与主内存的交互,JMM 定义了 8 种原子操作:
lock(锁定)
unlock(解锁)
read(从主内存读)
load(写入工作内存副本)
use(在执行引擎中使用变量值)
assign(在执行引擎中赋值)
store(把工作内存的值写回主内存)
write(更新到主内存变量)
👉 这些操作保证了 JMM 在多线程下的一致性和安全性。
4. JMM 解决的三个关键问题
可见性
一个线程修改了共享变量,其他线程能否马上看到?
用
volatile
、synchronized
、Lock
保证。
有序性
代码执行顺序是否和写的顺序一致?
编译器和 CPU 会 指令重排序,JMM 通过 happens-before 规则 来约束。
原子性
基本读写是原子的,但复合操作(如 i++)不是。
通过
synchronized
、Lock
或原子类保证。
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 规则。
实际保证手段:
volatile
、synchronized
、final
、Lock
。
什么是逃逸分析
好问题 👍,逃逸分析(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 分析对象是否会逃出方法或线程的过程。
优化手段:
栈上分配 → 减少 GC 压力
标量替换 → 拆分对象,甚至不创建对象
锁消除 → 去掉无必要的
synchronized