Kirovj's Chaos


JVM的激进编译

Posted on

public class NoVisibility {
    private static boolean flag;  
    private static int number;  
  
    private static class ReaderThread extends Thread {  
        public void run() {  
            while (!flag);
            System.out.println(number);  
        }  
    } 
  
    public static void main(String[] args) throws InterruptedException {  
        new ReaderThread().start();  
        Thread.sleep(1000);
        number = 42;  
        flag = true;  
        Thread.sleep(10000);
    }  
}

以上代码会死循环。

https://github.com/jonwinters/jmm-research

虚拟机的激进编译的问题,虚拟机编译的时候认为 flag 是一个非 volatile 的 static 变量,激进编译后的汇编代码就只从堆内存里面取了一次这个变量的值放到寄存器里面,然后寄存器的值不会再被更新,所以就造成了死循环。

反汇编看一下就知道是虚拟机 JIT 的问题,这种问题本来就是匪夷所思的,对于 static 非 volatile 变量,JVM 编译成本地代码的时候偷懒了,JVM 认为 static 非 volatile 变量不值得 反复通过内存寻址到堆内存去读取它的值,所以只读取了一次 放到 CPU 的寄存器里面就完事了,这样 CPU 永远观测不到最新的值,在某些低版本的 JDK8 或者 ARM 平台 这个代码是可以退出的。