JVM 將 Java 字節碼編譯為機器碼運行在 CPU 上。**CPU 微架構特性(緩存層級、內存重排、原子指令、NUMA 拓撲)直接決定 Java 並發程式的實際效能與正確性。** 理解這些機制,能寫出真正高效的並發代碼,而非盲目套用 API。
| 層級 | 典型延遲 | 容量 | 共享範圍 |
|---|---|---|---|
| L1 數據緩存 | 1-2 ns (≈ 4-8 週期) | 32-64 KB | 核心獨享 |
| L2 緩存 | 3-10 ns | 256 KB - 1 MB | 核心獨享/雙核共享 |
| L3 緩存 (LLC) | 30-50 ns | 8-64 MB | 插槽/封裝共享 |
| 主內存 (DRAM) | 70-150 ns | GB-TB | 全系統共享 |
關鍵洞見:L1 命中比主內存快 **100 倍**。緩存未命中導致流水線停頓,是高併發效能殺手。
多線程修改**同一緩存行內不同變量**,導致緩存一致性協議 (MESI) 在核心間頻繁傳輸緩存行(RFO 請求),效能崩塌。
// 災難:計數器陣列,每線程一個,但挤在同一緩存行
class BadCounter {
private int[] counters = new int[THREAD_COUNT]; // 相鄰 int 共享緩存行
public void increment(int idx) { counters[idx]++; }
}
// 解決:填充到 64 字節對齊
class GoodCounter {
static class PaddedInt {
volatile int value;
long p1, p2, p3, p4, p5, p6, p7; // 7*8=56 + 4 = 60 字節,接近 64
}
private final PaddedInt[] counters;
public GoodCounter(int n) { counters = new PaddedInt[n]; for (int i=0;i
JDK 8+ 內建解決:`@Contended` 註解 (需 `-XX:-RestrictContended`) 或 `LongAdder` 內部分段填充。
| 屏障 | 指令示例 | 語義 |
|---|---|---|
| LoadLoad | `lfence` | 載入不重排 |
| StoreStore | `sfence` | 存儲不重排 |
| LoadStore | `lfence; sfence` | 載入不越過存儲 |
| StoreLoad (全屏障) | `mfence` / `lock addl $0,0(%rsp)` | 存儲不越過載入,**最昂貴** |
volatile 寫:StoreStore + StoreLoad(發布前刷新)volatile 讀:LoadLoad + LoadStore(獲取後無效本地緩存)synchronized 進入/退出:全屏障Unsafe.putOrderedXxx:StoreStore(釋放語義,無 StoreLoad,比 volatile 寫快)VarHandle:`setRelease`/`getAcquire`/`setVolatile`/`getVolatile` 精確控制
// C++ 伪代码對應
lock cmpxchg [mem], reg // 原子比較交換,隱含全屏障
lock xadd [mem], reg // 原子獲取並增加
lock add [mem], 1 // 原子增加
Java `AtomicInteger.incrementAndGet()` 最終編譯為 `lock xadd`。
| 工具 | 用途 |
|---|---|
| JMH | 微基準測試、吞吐/延遲分析 |
| perf / VTune | CPU 週期、緩存未命中、分支預測失敗 |
| JITWatch | 查看 JIT 編譯代碼、內聯決策 |
| hsdis | 反彙編 JIT 生成機器碼 |
| Java Flight Recorder (JFR) | 生產環境低開銷剖析、鎖分析 |
| jemalloc / async-profiler | 內存分配、CPU 火焰圖 |