从 Java 代码到 CPU 指令
源文件被编译成字节码后就与平台无关了,这也是 java 的特性之一。JVM 会把字节码转换为机器指令,机器指令可以在 CPU 上运行,也就是最终的程序执行。
JVM 内存结构 vs Java 内存模型 vs Java 对象模型
JVM 内存结构
JVM 内存结构与 java 虚拟机的运行时区域有关。
- 绿色区域:方法区和堆是各个线程共享的。
- 黄色区域:java 栈、本地方法栈和程序计数器是各个方法私有的。
堆
堆是运行时占用区域最大的一块儿,也是占用内存最多的。通过 new 或者其他指令创建的实例对象存储在堆(Heap)中,如果这些对象不再被引用,会被垃圾回收。数组也包含在内,java 中数组也是对象。堆的优势在于运行时会动态分配,没有必要事先指定大小。
虚拟机栈
即 java 栈,虚拟机栈中保存了各个基本的数据类型,以及对于对象的引用。编译时就确定了大小。
方法区
方法区存储已经加载的 static 静态变量或者类信息或常量信息,以及永久引用。
本地方法栈
本地方法栈保存本地方法相关,主要指的 native 方法。
程序计数器
保存当前线程所执行的字节码的行号数,上下文切换时的数据也会被保存,异常、循环指令等也依赖程序计数器。
Java 对象模型
java 对象模型,和 java 对象在虚拟机中的表现形式有关。java 是面向对象的,每一个对象在 Jvm 的存储是有一定结构的。
JMM
JMM 全称为 Java Memory Model,即 Java 内存模型。由于不同处理器的内存处理结果不一样,因此无法保证并发安全。因此 java 给出了一个标准,使得多线程的运行结果处于可预期的状态,无论处理器是什么样,都能达成统一,使得处理结果一致。
JMM 除了是上述的一种规范外,它还是关键字的原理。volatile、synchronized 和 lock等底层实现原理都是 JMM。
重排序
public class OutOfOrderExecution {
private static int x=0,y=0;
private static int a=0,b=0;
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(() -> {
a = 1;
x = b;
});
Thread thread2 = new Thread(() -> {
b = 1;
y = a;
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("x=" + x + ",y=" + y);
}
}
上面这段代码根据其运行顺序的不同可能出现 3 种结果:
- thread1 先运行:x=0,y=1;
- thread2 先运行:x=1,y=0;
- thread1 的第一行和 thread2 的第一行先运行:x=1,y=1。
但是,如果执行多次就会发现,还会出现 x =0,y=0 的情况,这时,线程内部的代码顺序发生了改变,即先给 x 和 y 赋值,再给 a 和 b 赋值。
重排序:线程内部代码的执行顺序和 java 中代码的编码顺序不一致,代码指令并不是严格按照代码语句顺序执行的。
好处
- 重排序后会对代码指令进行优化,提高运行速度
重排序的三种情况
- 编译器优化:包括 JVM、JIT 编译器,处于优化目的进行重排序。
- CPU 指令重排:就算编译器不发生重排序,CPU 也会发生指令的重排序,通过乱序达到效率提升。
- 内存的“重排序”:线程 A 的修改线程 B 看不到,引出可见性问题。