前言:
深入學習JAVA前,程序猿需要了解一些相關的硬件底層知識,這一篇專門來講一講CPU和JAVA相關的知識
因為學習內容里有些不那么重要的知識點,往往就是截圖或者少量文字帶過,個人筆記不會記錄那么多細節,詳細資料請讀者自己查詢,見諒。
簡易的計算機組成:

CPU從PC中拿到下一條指令的地址,從內存或別的IO設備中讀取數據,把數據暫時存放在Registers中,根據指令要求,在ALU中對Registers中的數據進行運算,然后把結果返回到指定的內存區域。
CPU的基本組成:
- Registers -> 暫時存儲CPU計算需要用到的數據
- ALU -> Arithmetic & Logic Unit 運算單元
- CU -> Control Unit 控制單元
- MMU -> Memory Management Unit 內存管理單元
- cache -> 多級緩存
CPU的多線程結構:

ALU可以快速切換Registers,以快速處理多個線程
多核CPU結構:

CPU緩存:
為了讓CPU更快速的讀取數據(比從內存讀取還快),就在CPU中設計了多級緩存

讀取速度參考值:

按塊讀取規則
根據程序局部性原理(一個程序存儲的數據一般在內存中是連續的),往往讀取數據都是按照一個個數據塊進行讀取的,以此來提高IO效率。
cache line (緩存行)

上面是個多核的CPU的內存讀取模型
CPU讀取數據時,會就近原則取數據,取不到最后會從內存中再一步步存回L1緩存
CPU->L1->L2->L3->MEMORY->L3->L2->L1->CPU
cache line 為 緩存行 ,每次讀取數據都會讀取一個緩存行,而不是單個數據大小的內容。
緩存行越大,局部性空間效率越高,但讀取時間慢
緩存行越小,局部性空間效率越低,但讀取時間快
取一個折中值,目前CPU緩存行大多使用:64字節
多個CPU讀取同一緩存行內的數據存在數據一致性問題:
對於這個問題,不同的CPU使用不同的緩存一致性協議,單位都是以緩存行讀取的。
Intel:MESI協議 www.cnblogs.com/z00377750/p…
- M:Modified
- E:Exclusive
- S:Shared
- I:Invalid
偽共享問題:
緩存系統中是以緩存行(cache line)為單位存儲的,當多線程修改互相獨立的變量時,如果這些變量共享同一個緩存行,就會無意中影響彼此的性能,這就是偽共享。
緩存行對齊:
因為有緩存行的存在,就出現了一種編程設計方式,叫做緩存行對齊!
對於有些特別敏感的數字,會存在線程高競爭的訪問,為了保證不發生偽共享,就可以使用緩存航對齊的編程方式
JDK7中,很多采用long padding提高效率(就是一個數據的前后塞滿Long類型,一個Long占8字節)
JDK8,加入了@Contended注解(實驗)需要加上:JVM -XX:-RestrictContended
CPU的亂序執行特性:
當CPU得到的多個指令間沒有依賴關系,那么CPU會在等待某條指令操作時優先執行另一條指令,上圖有個很好的例子,燒開水的時候,人可以去洗茶杯茶壺,這樣就可以提高喝茶的整體效率了~
但是,當在多線程場景中,CPU亂序執行可能就會出現問題
這里簡單快速的解釋一下(主要跟JVM有關)
1、由於JAVA創建對象時,有個中間態,處於中間態時,對象並沒有完成全部的初始化。
2、如果此時,在中間態時,發生了CPU亂序執行,初始化對象指令還未執行,對象就被另一個線程使用,就會導致拿到沒有初始化完成的對象。
(DCL單例就是解決這個問題,所以DCL單例必須使用volatile修飾對象, volatile可以禁止重排序,並且線程透明)
禁止亂序執行:
CPU層面上,要如何禁止指令的重排序呢?答案就是內存屏障。,對某部分內存做操作前后做出內存屏障,屏障前后指令不可亂序執行。
Intel CPU可以使用三種原語( lfence sfence mfence ),或者Lock指令來實現。
1、lfence -> loadfence 在lfence指令前的讀操作必須在lfence指令后的讀操作前執行,sfence -> storefence 在sfence指令前的寫操作必須在sfence指令后的寫操作前執行, mfence -> mixedfence 在mfence指令前的讀寫操作必須在mfence指令后的讀寫操作前執行
2、Lock指令比較狠,它屬於X86 CPU指令,它會直接鎖住內存總線(Memory BUS)
JVM層面上,JVM規范了他的內存屏障實現
1、loadload屏障 loadload屏障前的load1指令必須先於loadload屏障后的load2指令前完成,不可互換
2、storestore屏障 。。。。。。類推
3、loadstore屏障 loadstore屏障前的load1指令必須先於loadload屏障后的store2指令前完成,不可互換
4、storeload屏障 。。。。。。類推
舉個栗子:JVM對volatile的實現,保證了可見性和禁止亂序
store1
storestore (等前面的store1寫指令完成后才能開始volatile的寫指令)
volatile寫指令
storeload (等前面的volatile寫指定完成后才能做后面的讀指令)
load1
loadload (等前面的load1寫指令完成后才能開始volatile的讀指令 )
volatile讀指令
loadstore (等前面的volatile讀指令完成后才能做后面的寫指令)
JVM同時也規定了指令重排序的必須遵守的8條規則,稱之為Happens-Before規則(這個知道就好,不用細扣)
Happens-Before的規則包括:
- 程序順序規則
- 鎖定規則
- volatile變量規則
- 線程啟動規則
- 線程結束規則
- 中斷規則
- 終結器規則
- 傳遞性規則
as-if-serial:不管硬件什么順序,單線程執行的結果不變,看上去好像是serial順序執行
SingleThreadPool 可以實現單線程的隊列任務
WC - Write Combining 合並寫技術
為了提高寫效率,CPU在寫入L1緩存時,同時用WC寫入L2緩存
Registers和L1緩存間存在極小空間的緩沖區,Load Buffer, Store Buffer
但是還有一個直接通向L2緩存,那就是WC Buffer,這塊緩沖區一般大小為4個字節。在寫入L1緩存的同時,寫入一個WC BUFFER,寫滿該緩存區后,直接更新到L2緩存
UMA 和 NUMA
均勻存儲器存取(Uniform-Memory-Access,簡稱UMA)模型、非均勻存儲器存取(Nonuniform-Memory-Access,簡稱NUMA)模型,這些模型的區別在於存儲器和外圍資源如何共享或分布。
結束語
CPU相關的知識就記錄到這,總的來說,一些知識點解釋了JVM的一些底層細節,也非常有趣,作為以后了解JVM原理時的基礎知識,還是非常有學習意義的。