JVM
知识点汇总

什么是Java虚拟机?为什么Java被称作是“平台无关的编程语言”?
Java虚拟机是一个可以执行Java字节码的虚拟机进程。Java源文件被编译成能被Java虚拟机执行的字节码文件。Java被设计成允许应用程序可以运行在任意一个安装了Java虚拟机的平台上,而不需要程序员为每一个平台单独重写或者是重新编译,Java虚拟机知道底层硬件平台的指令长度和其他特性。
JMM(Java Memory Model)
为什么要有内存模型? 在传统计算机硬件内存架构中由于 CPU 和主内存间存在数量级的速率差,于是就引入了多级高速缓存来解决这个问题,但是多级缓存的引入又带来了缓存一致性的问题,为了解决缓存一致性问题,就抽象出了 JMM.
JMM 同步规定:
- 线程解锁前,必须把共享变量的值刷新回主内存
- 线程加锁前,必须读取主内存的最新值到自己的工作内存
- 加锁解锁是同一把锁
由于 JVM 运行程序的实体是线程,而每个线程创建时 JVM 都会为其创建一个工作内存(每个线程的私有数据区域),而 Java 内存模型中规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可访问,但线程对变量的操作必须在工作内存中进行。
首先要将变量从主内存拷贝到自己的工作空间,然后对变量进行操作,操作完成再将变量写回主内存,不能直接操作主内存中的变量,不同的线程无法访问对方的工作内存,线程之间的通讯必须通过主内存来完成,其简要访问过程如下图:

为了更精准控制工作内存和主内存间的交互,JMM 还定义了八种操作:
lock(锁定):作用于主内存的变量,把一个变量标识为一条线程独占的状态。
unclock(解锁):作用于主内存的变量,把一个处于锁定的状态释放出来。
read(读取):作用于主内存的变量,把一个变量的值从主内存传输到线程的工作内存中
load(载入):作用于工作内存的变量,把read操作从主内存得到的变量值放入工作内存的变量副本中。
use(使用):作用于工作内存的变量,把工作内存中一个变量的值传递给执行引擎
assign(赋值):作用于工作内存的变量,把一个从执行引擎接收到的值赋值给工作内存的变量
store(存储):作用于工作内存的变量,把工作内存中的一个变量的值传递到主内存,以便write操作使用。
write(写入):作用于主内存的变量,把store操作从工作内存中得到的变量的值放入主内存的变量中。
JMM 是定义程序中变量的访问规则,线程对于变量的操作只能在自己的工作内存中进行,而不能直接对主内存操作。由于指令重排,读写的顺序会被打乱,因此 JMM 需要保证原子性,可见性,有序性。主内存可以认为就是物理内存,而 工作内存就是CPU缓存
mermaid flowchart LR 2["`基本数据类型读写,除long、double`"] 3["`synchronized`"] 4["`volatile`"] 5["`happens-before`"] 6(("`原子性`")) 7(("`可见性`")) 8(("`有序性`")) 2-->6 3-->6 3-->7 4-->7 4-->8 5-->8
原子性:Java内存模型通过上述8种操作(基本数据类型读写)来保证原子性操作,其中 lock 和 unlock 对应着 synchronized 关键字 可见性:当一个线程修改了某个变量的值,其他线程立即可以看到这个修改,这就是内存的可见性。通过volatile、synchronized、final来保证可见性。 有序性:由于处理器和编译器的重排序导致的有序性问题,通过volatile和happens-before来保证。
happens-before 原则:
- 程序次序规则:一个线程内,按照代码顺序运行
- 锁定规则:一个unLock操作先行发生于后面对同一个锁lock操作
- volatile 变量规则:对一个volatile变量的写操作先行发生于后面对这个变量的读操作
- 传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C
- 线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作
- 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生
- 线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行
- 对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始