這兩個概念估計有很多人會混淆,它們均可以說是 JVM 規範的一部分,但真不是一回事!它們描述和解決的是不一樣問題,簡單來講,程序員
JVM 是什麼呢?它屏蔽了底層架構的差別性,是 Java 跨平臺的依據,也是每一個 Java 程序員必須瞭解的一部分。編程
Java Virtual Machine(JVM) 是一種抽象的計算機,基於堆棧架構,它有本身的指令集和內存管理。它加載 class 文件,分析、解釋並執行字節碼。基本結構以下:數組
如上圖所示,JVM 主要分爲三個子系統:類加載器、運行時數據區和執行引擎。緩存
它主要功能是處理類的動態加載,還有連接,而且在第一次引用類時進行初始化。數據結構
Loading - 加載,顧名思義,用於加載類,它有三種類加載器,根據雙親委託模型,從不一樣路徑進行加載:多線程
Linking - 連接,動態連接到運行時所需的資源,分爲三步:架構
Initialization - 類初始化,類加載的最後階段,這裏對靜態變量進行賦值,並執行靜態塊。(注意區分對象初始化)併發
它約定了在運行時程序代碼的數據好比變量、參數等等的存儲位置,主要包含如下幾部分:app
運行時數據區存儲着要執行的字節碼,執行引擎將會讀取並逐個執行。函數
Interpreter - 解釋器,它對字節碼的解釋很快,但執行慢,有個缺點是,當方法被屢次調用時,每次都須要從新解釋。
JIT Compiler- JIT編譯器, 解決了解釋器的缺點,仍使用解釋器來轉換字節代碼,但發現有代碼重複執行時,會使用 JIT 編譯器,將整個字節碼編譯成本地代碼,將本地代碼用於重複調用,從而提升系統的性能,有如下幾部分組成:
Garbage Collector- 垃圾收集器,收集和刪除未引用的對象。
另外,還包括執行引擎所需的本地庫*(Native Method Libraries)和與其交互的 JNI 接口(Java Native Interface)*。
如今來看下 Java 內存模型和 JVM 內存結構有何不一樣。
常說的 JVM 內存結構指的就是上文提交到運行時數據區,其中堆、方法區被線程共享,程序計數器、棧、運行時常量池被線程獨享。
它描述的是,在運行時,字節碼和代碼數據存儲的位置。
先拋開 Java 不說,先來看下內存模型是什麼?維基百科中的定義:
In computing, a memory model describes the interactions of threads through memory and their shared use of the data.
意思就是,在計算中,內存模型描述了多線程如何正確的經過內存進行交互和使用共享數據。換句話說,內存模型約束了處理器對內存的讀寫。
CPU 和內存之間一般會存在一層或多層高速緩存,這對單處理器可能沒問題,但在多處理器系統中,可能就會出現緩存一致性問題,也就是當兩個處理器(線程)同時讀取相同內存位置會發生什麼?什麼狀況下會看到相同的值?
緩存一致性問題,在併發編程中,又被稱做可見性問題。內存模型在處理器級別,爲處理器彼此之間對內存寫入結果的可見性,定義了充分必要條件:
大多數處理器不會限制內存操做的順序,多線程在執行時可能會出現讓人困惑和違背直覺的結果。這是由於 CPU 爲了充分利用不一樣類型存儲器(寄存器、高速緩存、主存)的總線帶寬,會將內存操做從新排序,以無序執行,這個動做稱爲內存排序或指令重排序。
重排序,也被稱爲編譯器優化和處理器優化,由於它既能夠發生在編譯期間,也能夠發生在 CPU 運行時。爲了保證多線程的有序性,須要使用內存屏障禁止重排序。
因此說,內存模型就是在硬件層面描述了使用內存屏障(刷新緩存或禁用指令重排序)解決多線程編程中的可見性和有序性的問題。
Java 內存模型(下文簡稱 JMM)就是在底層處理器內存模型的基礎上,定義本身的多線程語義。它明確指定了一組排序規則,來保證線程間的可見性。
這一組規則被稱爲 Happens-Before, JMM 規定,要想保證 B 操做可以看到 A 操做的結果(不管它們是否在同一個線程),那麼 A 和 B 之間必須知足 Happens-Before 關係:
怎麼理解 happens-before 呢?若是按字面意思,好比第二個規則,線程(不論是不是同一個)的解鎖動做發生在鎖定以前?這明顯不對。happens-before 也是爲了保證可見性,好比那個解鎖和加鎖的動做,能夠這樣理解,線程1釋放鎖退出同步塊,線程2加鎖進入同步塊,那麼線程2就能看見線程1對共享對象修改的結果。
Java 提供了幾種語言結構,包括 volatile, final 和 synchronized, 它們旨在幫助程序員向編譯器描述程序的併發要求,其中:
編譯器在遇到這些關鍵字時,會插入相應的內存屏障,保證語義的正確性。
有一點須要注意的是,synchronized 不保證同步塊內的代碼禁止重排序,由於它經過鎖保證同一時刻只有一個線程訪問同步塊(或臨界區),也就是說同步塊的代碼只需知足 as-if-serial 語義 - 只要單線程的執行結果不改變,能夠進行重排序。
因此說,Java 內存模型描述的是多線程對共享內存修改後彼此之間的可見性,另外,還確保正確同步的 Java 代碼能夠在不一樣體系結構的處理器上正確運行。
它們之間的關係能夠這樣來個總結,實現一個 JVM 要知足內存結構描述的組成部分,設計如何執行多個線程的時候,要知足Java 內存模型約定的多線程語義。
搜索公衆號「頓悟源碼」獲取更多源碼分析和造的輪子。