首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 程式設計綜合問答

Java 線程可見性陷阱:64 位 JVM 下 while(!stop) 不退出的 JIT 編譯優化分析

     編輯:程式設計綜合問答

現象復現

static class IRun implements Runnable {
    boolean exec = true;
    public void setStop() { exec = false; }
    public void run() {
        int c = 0;
        while (exec) { c++; } // 空循環
        System.out.println("退出了循環");
    }
}

主線程 sleep 1s 後調用 setStop(),32 位 JVM 正常退出,**64 位 JVM (Server VM) 永不退出**。

根因:JIT 熱點編譯 + 逃逸分析

  1. 運行 ~10k 次後,C2 編譯器將 run() 編譯為機器碼。
  2. 逃逸分析發現 exec 為實例字段,無同步、無 volatile,判定**線程局部不可變**。
  3. 編譯器將循環優化為 while(true) { c++; },完全移除字段讀取。
  4. 主線程修改 exec 寫入堆內存,編譯後代碼**永不重讀**,陷入死循環。

字節碼證據

反編譯 class 文件顯示 while(exec) 編譯為 ifeq 跳轉,**非** while(true),證明**源碼級未優化**,優化發生在 **JIT 機器碼層面**。

為何加入 String s = new String() 即正常?

  • 對象分配觸發 **Safe Point**,JIT 必須保留內存屏障/字段讀取語義,無法假設字段不變。
  • 任何方法調用、I/O、同步、volatile 讀寫、對象分配均可阻止該激進優化。

正確修復

volatile boolean exec = true; // 強制每次從主內存讀取,禁止寄存器緩存
// 或
AtomicBoolean exec = new AtomicBoolean(true);
// 或
synchronized 保護讀寫

最佳實踐

  • **所有線程共享的可變字段必須 volatile / atomic / synchronized**。
  • 使用 Thread.stop() / interrupt() 等標準協作機制,而非自定義標志位。
  • 壓測時加上 -XX:+PrintCompilation 觀察熱點編譯行為。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved