Java開發者需要了解的硬件知識 (一)、CPU篇


前言:

深入學習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)為單位存儲的,當多線程修改互相獨立的變量時,如果這些變量共享同一個緩存行,就會無意中影響彼此的性能,這就是偽共享。

https://zhuanlan.zhihu.com/p/124974025

https://www.cnblogs.com/cyfonly/p/5800758.html

緩存行對齊:

因為有緩存行的存在,就出現了一種編程設計方式,叫做緩存行對齊!

對於有些特別敏感的數字,會存在線程高競爭的訪問,為了保證不發生偽共享,就可以使用緩存航對齊的編程方式
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的規則包括:

  1. 程序順序規則
  2. 鎖定規則
  3. volatile變量規則
  4. 線程啟動規則
  5. 線程結束規則
  6. 中斷規則
  7. 終結器規則
  8. 傳遞性規則

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)模型,這些模型的區別在於存儲器和外圍資源如何共享或分布。

https://blog.csdn.net/tiangwan2011/article/details/7298785

 

 

 

 

 

結束語

CPU相關的知識就記錄到這,總的來說,一些知識點解釋了JVM的一些底層細節,也非常有趣,作為以后了解JVM原理時的基礎知識,還是非常有學習意義的。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM