1. 程式人生 > 實用技巧 >Java虛擬機器詳解(二)------執行時記憶體結構

Java虛擬機器詳解(二)------執行時記憶體結構

  首先通過一張圖瞭解Java程式的執行流程

  關於這幅圖涉及到的:

  ①、class檔案

  ②、類載入器

  ③、執行時資料區

  ④、執行引擎

  ⑤、垃圾回收器

  這都是接下來將要介紹的重點。

1、執行時資料區結構圖

  HotSpot JDK1.8定義的執行時資料區

  注意:HotSpot實現的執行時資料區和Java虛擬機器規範定義的還是有所不同的,

  ①、將Java虛擬機器棧和本地方法棧合二為一;

  ②、元資料區取代了方法區,並且元資料區不在Java虛擬機器中,而是在本地記憶體中。

  ③、執行時常量池由方法區中移到了堆中

2、程式計數器

  程式計數器(Program Conputer Register)這是一塊較小的記憶體空間,可以看作是當前執行緒所執行的位元組碼的行號指示器,在虛擬機器的概念模型裡,位元組碼翻譯器的工作就是通過改變這個計數器的值來選取下一條需要執行的位元組碼指令,分支、迴圈、跳轉、異常處理、執行緒恢復等基礎功能都需要依賴這個計數器來完成。

  1)執行緒私有:Java虛擬機器的多執行緒是通過執行緒輪流切換並分配處理器執行時間的方式來實現的,在任何一個確定的時刻,一個處理器都會執行一條執行緒中的指令。因此,為了執行緒切換後能恢復到正確的執行位置,每條執行緒都需要有一個獨立的程式計數器,各條執行緒之間計數器互不影響,獨立儲存,我們稱這類記憶體區域為“執行緒私有”的記憶體。

  2)記錄虛擬機器位元組碼指令的地址:如果執行緒執行的是java方法,則記錄,如果正在執行的是Native方法,則計數器則為空(Undefined)。

  3)不會拋OutOfMemoryError異常。

3、虛擬機器棧

  Java虛擬機器棧(Java Virtual Machine stack),這塊區域也是執行緒私有的,與執行緒同時建立,用於儲存棧幀。

虛擬機器棧描述的是Java方法執行的記憶體模型,每個方法在執行的同時都會建立一個棧幀(Stack Frame)由於儲存區域性變量表,運算元棧、動態連結、方法出口等資訊。

  每一個方法從呼叫直至執行完成的過程,就對應這一個棧幀在虛擬機器棧中入棧到出棧的過程。

  1)執行緒私有

  隨執行緒建立而建立,宣告週期和執行緒保持一致。

  2)由棧幀組成

  執行緒每個方法被執行的時候都會建立一個棧幀,用於儲存區域性變量表、操作棧、動態連結、方法出口等資訊,每一個方法被呼叫直至執行完成的過程,就對應著一個棧幀在虛擬機器棧中從入棧到出棧的過程。

  3)丟擲 StackOverflowError 和 OutOfMemoryError 異常

  如果執行緒請求的棧深度大於虛擬機器所允許的深度,將丟擲StackOverflowError 異常;如果虛擬機器棧可以動態擴充套件,當擴充套件時無法申請到足夠的記憶體時會丟擲OutOfMemoryError 異常。

4、本地方法棧

  本地方法棧(Native Method Stacks)作用和虛擬機器棧型別,虛擬機器棧執行的是Java方法,本地方法棧執行的是 Native 方法(如執行緒啟動方法start()是一個native方法,底層呼叫的是作業系統,所以Thread類啟動多執行緒,不是馬上執行,何時啟動是由作業系統決定的),本地方法棧也會丟擲丟擲 StackOverflowError 和 OutOfMemoryError 異常。

5、Java堆

  Java堆是Java虛擬機器所管理記憶體最大、被所有執行緒共享的一塊區域,目的是用來存放物件,基本上所有的額物件例項和陣列都在堆上分配(不是絕對)。Java堆也是垃圾回收器管理的主要區域。

  1)執行緒共享

  堆存放的物件,某個執行緒修改了物件屬性,另外一個執行緒從堆中獲取的該物件是修改後的物件,為什麼堆要設計成執行緒共享呢?

  我們可以假設堆是執行緒私有的,很顯然一個系統建立的物件會有很多,而且有些物件會比較大,如果設計成執行緒私有的,那麼如果有很多執行緒同時工作,那麼都必須給他們分配相應的私有記憶體,我相信記憶體很快就撐爆了,很顯然將堆設計為執行緒共享是最好不過了,不過凡事都具有兩面性,執行緒共享的設計這也帶來了多執行緒併發資源衝突問題,關於這個問題由於不是本系列部落格的主旨,這裡就不做詳細介紹了。

  2)存放物件

  3)垃圾收集

  Java堆也被稱為“GC堆”,是垃圾回收器的主要操作記憶體區域。當前垃圾回收器都是使用的分代收集演算法,所以Java堆還可以分為:新生代和老年代,而新生代又可以分為 Eden 空間、From Survivor 空間、To Survivor空間。這是為了更好的回收記憶體,關於垃圾回收演算法在後續部落格會詳細介紹

  4)拋StackOverflowError異常和OutOfMemoryError異常。

  在Java虛擬機器規範中,對這個區域規定了兩種異常狀況:如果執行緒請求的棧深度大於虛擬機器所允許的深度,將丟擲StackOverflowError異常;如果虛擬機器棧可以動態擴充套件,如果擴充套件時無法申請到足夠的記憶體,就會丟擲OutOfMemoryError異常。

6、方法區

  方法區不是存放方法的區域,是存放類模板的。

  

方法區(Method Area)用來儲存已被Java虛擬機器載入的類資訊、常量、靜態變數、即時編譯器編譯後的程式碼等資料。

  方法區也稱為“永久代”,這是因為垃圾回收器對方法區的垃圾回收比較少,主要是針對常量池的回收以及對型別的解除安裝,回收條件比較苛刻。經常會導致對此記憶體未完全回收而導致記憶體洩露,最後當方法區無法滿足記憶體分配時,將丟擲 OutOfMemoryError 異常。

  在JDK1.8 的 HotSpot 虛擬機器中,已經去掉了方法區的概念,用Metaspace 代替,並且將其移到了本地記憶體來規劃了。

7、執行時常量池

  在Java虛擬機器規範中,執行時常量池(Runtime Constant Pool)用於存放編譯期生成的各種字面量和符號引用,是方法區的一部分。但是Java虛擬機器規範對其沒有做任何細節的要求,所以不同虛擬機器實現商可以按照自己的需求來實現該區域,比如在 HotSpot 虛擬機器實現中,就將執行時常量池移到了堆中  

  ①、存放字面量、符號引用、直接引用

  通常來說,該區域除了儲存Class檔案中描述的引用外,還會把翻譯出來的直接引用也儲存在執行時常量池,並且Java語言並不要求常量一定只能在編譯器產生,執行期間也可能將常量放入池中,比如String類的intern()方法,當呼叫intern方法時,如果池中已經包含一個與該String確定的字串相同equals(Object)的字串,則返回該字串。否則,將此String物件新增到池中,並返回此物件的引用。

  ②、丟擲 OutOfMemoryError 異常

  執行時常量池是方法區的一部分,會受到方法區記憶體的限制,當常量池無法申請到記憶體時,會丟擲該異常。

8、直接記憶體

  直接記憶體(Direct Memory)並不是虛擬機器執行時資料區的一部分,它也不是Java虛擬機器規範定義的記憶體區域。我們可以看到在 HotSpot 中,就將方法區移除了,用元資料區來代替,並且將元資料區從虛擬機器執行時資料區移除了,轉到了本地記憶體中,也就是說這塊區域是受本機實體記憶體的限制,當申請的記憶體超過了本機實體記憶體,才會丟擲 OutOfMemoryError 異常