1. 程式人生 > >【jvm】jvm的記憶體模型

【jvm】jvm的記憶體模型

前言: jvm的記憶體模型,應該是每個"高階java"程式猿必備的知識儲備了,各大公司對高階的要求裡幾乎都有提到"深入理解Jvm的記憶體模型...",其實當你深入的理解了jvm的記憶體模型後,在以後程式碼的開發中,你會對變數,常量,類,方法的生命週期及作用域,異常等有更深入的理解,而不是像新手一樣生搬硬套final static關鍵字的作用...如果沒有這些知識儲備,在你開發中碰到一些bug,看上去程式碼沒啥問題,但卻報錯,然後你幾乎無從下手,不知道問題出在了哪裡...


計算機自誕生以來,發展極其迅速,以至於我們的電腦幾乎熬不過5年就要換新的了...在頻繁的更迭中,電腦的cpu變得越來越強大,運算速度幾何倍的增長,於是磁碟的讀寫速度跟不上cpu的處理速度,記憶體開始登山舞臺,再後來記憶體也吃不消cpu這膨脹的效能了,為了不被記憶體拖慢cpu效能,cpu產商開始為每個cpu提供一塊高速緩衝記憶體,來緩解記憶體IO速度跟不上cpu處理速度的尷尬.所以我們曾用過的單核cpu電腦實際上是下圖這樣進行資料運算的:

 

再後來,cpu大哥覺得一個人單槍匹馬太累了,需要有三頭六臂才能滿足人類對效能的要求,於是出現了多核CPU,多核CPU效能確實比原來提高太多,且基於快取記憶體的儲存互動很好的解決了處理器與記憶體之間的矛盾,但也引入了新的問題:快取一致性問題.在多處理器系統中,每個處理器有自己的快取記憶體,而他們又共享同一塊記憶體當多個處理器運算都涉及到同一塊記憶體區域的時候,就有可能發生快取不一致的現象。為了解決這一問題,需要各個處理器執行時都遵循一些協議,在執行時需要將這些協議保證資料的一致性。這類協議包括MSI、MESI、MOSI、Synapse、Firely、DragonProtocol等。如下圖所示:

之所以講這麼多硬體相關的東西,主要就是為了幫助大家理解jvm的虛擬機器.

其實java虛擬機器的記憶體模型是根據硬體設計的,基本上與計算機硬體保持一致,cpu處理執行緒,每個執行緒擁有自己的工作記憶體(類似於cpu的快取記憶體),然後所有執行緒共享一塊主記憶體.

 

當執行緒與記憶體區域進行互動時,資料從主存拷貝到工作記憶體,進而交由執行緒處理(操作碼+運算元).

下面我們一起來看下jvm的"邏輯"記憶體模型圖:

之所以稱之為邏輯記憶體模型圖,主要是因為是抽象出來的,並不是說Jvm的記憶體就跟上圖一模一樣實際存在的.

下面我們分別來看看這些元件分別是用來做什麼的:

1.方法區:

用來存放一些已經被虛擬機器載入的類資訊,常量,靜態變數,即時編譯後的程式碼等資料,是各個執行緒共享的一塊記憶體區域.

方法區的垃圾回收是極少的,這個區域的記憶體回收目標主要是針對常量池的回收和對型別的解除安裝,一般來說這個區域的回收“成績”比較難以令人滿意,尤其是型別的解除安裝,條件相當苛刻,但是這部分割槽域的回收確實是有必要的。

根據Java 虛擬機器規範的規定,當方法區無法滿足記憶體分配需求時,將丟擲OutOfMemoryError 異常.

2.java堆

在java中,堆是佔用記憶體最多的一塊區域,對應記憶體上一塊邏輯上連續空間,物理上不一定連續,由所有執行緒所共享,主要用來存放物件例項,幾乎所有的物件例項化都在此塊區域分配記憶體,這塊區域也是垃圾回收的主要區域,這塊區域由young區和Old區構成,如果在堆中沒有記憶體完成例項分配,並且堆也無法再擴充套件時,將會丟擲OutOfMemoryError 異常.

關於堆 更詳細的可以參閱我另外一篇文章,專門介紹jvm垃圾回收的【jvm】jvm調優.

3.Java 虛擬機器棧

java虛擬機器棧是執行緒私有的,生命週期與執行緒相同,跟執行緒同生共死,虛擬機器棧描述的是Java 方法執行的記憶體模型:每個方法被執行時都會建立一個棧幀,用來存放區域性變量表,操作棧,動態連結,方法出口等資訊,每一個方法被呼叫直至完成對應著一個棧幀從虛擬機器棧中從入棧到出棧的過程.

區域性變量表存放了編譯期可知的各種基本資料型別(boolean、byte、char、short、int、float、long、double)、物件引用和returnAddress 型別.在Java 虛擬機器規範中,對這個區域規定了兩種異常狀況:如果執行緒請求的棧深度大於虛擬機器所允許的深度,將丟擲StackOverflowError 異常;如果虛擬機器棧可以動態擴充套件(當前大部分的Java 虛擬機器都可動態擴充套件,只不過Java 虛擬機器規範中也允許固定長度的虛擬機器棧),當擴充套件時無法申請到足夠的記憶體時會丟擲OutOfMemoryError 異常。

4.程式計數器

程式計數器是一塊很小的記憶體空間,可以理解為它是當前執行緒所執行位元組碼的行號指示器.位元組碼直譯器工作時就是靠這個程式計數器的值來決定下一步要執行的位元組碼指令,分支,迴圈,跳轉,異常處理,執行緒恢復等都要依賴這個計數器來完成.為了保證互不影響,程式計數器被每個執行緒私有,如果正在執行的是Natvie 方法,這個計數器值則為空(Undefined)。此記憶體區域是唯一一個在Java 虛擬機器規範中沒有規定任何OutOfMemoryError 情況的區域。

5.本地方法棧

本地方法棧(Native Method Stacks)與虛擬機器棧所發揮的作用是非常相似的,其區別不過是虛擬機器棧為虛擬機器執行Java 方法(也就是位元組碼)服務,而本地方法棧則是為虛擬機器使用到的Native 方法服務,語言不限...

 


關於Jvm的記憶體模型就簡單介紹到這裡,參考資料《深入理解java虛擬機器》.