search
尋找貓咪~QQ 地點 桃園市桃園區 Taoyuan , Taoyuan

JVM總結之內存區域

Java虛擬機在執行Java程序的過程中會把它所管理的內存劃分為若干個不同的數據區域。這些區域都有各自的用途,有的區域是線程共享的,有的區域是線程隔離的。如下圖:

程序計數器

程序計數器(Program Counter Register)是一塊較小的內存空間,它可以看做是當前線程所執行的位元組碼的行號指示器。位元組碼解釋器工作時就是通過這個計數器的值來選取下一條需要執行的位元組碼指令,分支、循環、跳轉、異常處理、線程恢復等基礎功能都需要依賴這個計數器來完成。

程序計數器是線程私有的內存區域,保證線程切換后能恢復到正確的執行位置。

執行Java方法的時候,這個計數器記錄的是正在執行的虛擬機位元組碼的指令的地址;執行native方法的時候,計數器的值為空(null)。

Java虛擬機棧

Java虛擬機棧(Java Virtual Machine Stacks)描述的是Java方法執行的內存模型:每個方法在執行的同時都會創建一個棧幀(Stack Frame)用於存儲局部變數表、操作數棧、動態鏈接、方法出口燈信息。每個方法從調用直至執行完成的過程,就對應著一個棧幀在虛擬機棧中入棧到出棧的過程。

Java虛擬機棧也是線程私有的內存區域,生命周期和線程相同。

棧幀

在活動線程中,只有虛擬機棧頂的棧幀才是有效的,稱為當前棧幀(Current Stack Frame),這個棧幀所關聯的方法稱為當前方法(Current Method)。執行引用所運行的所有位元組碼指令都只針對當前棧幀進行操作。棧幀的概念結構如下圖所示:

局部變數表:局部變數表是一組變數值存儲空間,用於存放方法參數和方法內部定義的局部變數。在Java程序編譯為Class文件時,就在方法表的Code屬性的max_locals數據項中確定了該方法需要分配的最大局部變數表的容量。

操作數棧:操作數棧也常被稱為操作棧,它是一個后入先出棧。同局部變數表一樣,操作數棧的最大深度也是編譯的時候被寫入到方法表的Code屬性的max_stacks數據項中。操作數棧的每一個元素可以是任意Java數據類型,包括long和double。32位數據類型所佔的棧容量為1,64位數據類型所佔的棧容量為2。棧容量的單位為「字寬」,對於32位虛擬機來說,一個」字寬「佔4個位元組,對於64位虛擬機來說,一個」字寬「佔8個位元組。

動態鏈接:每個棧幀都包含一個指向運行時常量池中該棧幀所屬性方法的引用,持有這個引用是為了支持方法調用過程中的動態連接。在Class文件的常量池中存有大量的符號引用,位元組碼中的方法調用指令就以常量池中指向方法的符號引用為參數。這些符號引用一部分會在類載入階段或第一次使用的時候轉化為直接引用,這種轉化稱為靜態解析。另外一部分將在每一次的運行期期間轉化為直接引用,這部分稱為動態連接。:

返回地址:在方法退出之前,都需要返回到方法被調用的位置,程序才能繼續執行,方法返回時可能需要在棧幀中保存一些信息,用來幫助恢復它的上層方法的執行狀態。一般來說,方法正常退出時,調用者PC計數器的值就可以作為返回地址,棧幀中很可能會保存這個計數器值。而方法異常退出時,返回地址是要通過異常處理器來確定的,棧幀中一般不會保存這部分信息。方法退出的過程實際上等同於把當前棧幀出棧,因此退出時可能執行的操作有:恢復上層方法的局部變數表和操作數棧,把返回值(如果有的話)壓入調用都棧幀的操作數棧中,調用PC計數器的值以指向方法調用指令後面的一條指令等。

java虛擬機棧可以拋出如下異常:

  • StackOverflowError:線程請求的棧深度大於JVM允許的深度,拋出該異常;
  • OutOfMemoryError:如果虛擬機棧可以動態擴展,當擴展時無法申請到足夠的內存,拋出該異常。

本地方法棧

本地方法棧與虛擬機棧的作用是相似的,它們的區別是:

  • 虛擬機棧為JVM執行的Java方法服務;
  • 本地方法棧為JVM使用到的Native方法服務。

與虛擬機棧一樣,本地方法棧也會拋出StackOverflowError和OutOfMemoryError。

Java堆

Java堆是Java虛擬機所管理的內存最大的一塊,也是垃圾收集器管理的主要區域。

Java堆是所有線程共享的。

為了更好的回收內存,或者更好的分配內存,將Java堆細分為新生代和老年代。

  • 新生代(還可細分為den區、Form Survivor區和To Survivor區)
  • 老年代

關於每個區如何回收內存,何時回收內存在下一篇中再總結。

堆的大小也是可以調整的,可以通過虛擬機參數-Xmx和-Xms控制。同樣,在該區域,如果沒有內存分配給對象實例,並且堆也無法再擴展,會拋出OutOfMemoryError異常。

方法區

方法區主要用來存儲已經被JVM載入的類信息、常量、靜態變數等。

在過去(當自定義類載入器使用不普遍的時候),類幾乎是「靜態的」並且很少被卸載和回收,因此類也可以被看成「永久的」。另外由於類作為JVM實現的一部分,它們不由程序來創建,因為它們也被認為是「非堆」的內存。

方法區也是所有線程共享的內存區域。

方法區和Java堆一樣,也是需要進行垃圾回收的,該區域的內存回收目標主要是針對常量池的回收的和對類型的卸載。

當方法區無法滿足內存分配需求時,將拋出OutOfMemoryError異常。

Hotspot廢除永久代

永久代的問題

永久代是Hotspot中的一個概念,其他JVM的實現未必有,例如JRockit就沒有(只要不觸碰進程可用內存上限就不會出問題)。Hotspot使用在內存中劃分出一塊區域來存儲類的元信息、類變數以及內部字元串等,稱為永久代,把它作為方法區來使用。

永久代是一段連續的內存空間,我們在JVM啟動之前可以通過設置-XX:MaxPermSize的值來控制永久代的大小,32位機器默認的永久代的大小為64M,64位的機器則為85M。永久代的垃圾回收和老年代的垃圾回收是綁定的,一旦其中一個區域被佔滿,這兩個區都要進行垃圾回收。

  • 問題1:由於我們可以通過‑XX:MaxPermSize 設置永久代的大小,一旦類的元數據超過了設定的大小,程序就會耗盡內存,並出現內存溢出錯誤(OOM)。
  • 問題2:永久代中的元數據可能會隨著每一次Full GC發生而進行移動。並且為永久代設置空間大小也是很難確定的,因為這其中有很多影響因素,比如類的總數,常量池的大小和方法數量等。
  • 問題3:HotSpot虛擬機的每種類型的垃圾回收器都需要特殊處理永久代中的元數據。

迎來元空間

Hotspot在Java8中將類的元數據移到了一個與堆不相連的本地內存區域,這個區域就是元空間

由於類的元數據分配在本地內存中,元空間的最大可分配內存就是系統可用內存空間。用戶可以通過-XX:MaxMetaspaceSize為元空間設置一個空間可用最大值,如果不進行設置,JVM會根據類的元數據大小動態增加元空間的容量。

每一個類載入器的存儲區域都稱作一個元空間,所有的元空間合在一起就是我們一直說的元空間。當一個類載入器被垃圾回收器標記為不再存活,其對應的元空間會被回收。在元空間的回收過程中沒有重定位和壓縮等操作。但是元空間內的元數據會進行掃描來確定Java引用。

元空間虛擬機負責元空間的分配,其採用的形式為組塊分配。組塊的大小因類載入器的類型而異。在元空間虛擬機中存在一個全局的空閑組塊列表。當一個類載入器需要組塊時,它就會從這個全局的組塊列表中獲取並維持一個自己的組塊列表。當一個類載入器不再存活,那麼其持有的組塊將會被釋放,並返回給全局組塊列表。

元空間存在的問題

元空間虛擬機採用了組塊分配的形式,同時區塊的大小由類載入器類型決定。類信息並不是固定大小,因此有可能分配的空閑區塊和類需要的區塊大小不同,這種情況下可能導致碎片存在。元空間虛擬機目前並不支持壓縮操作,所以碎片化是目前最大的問題。

參考資料:

《深入理解Java虛擬機》



熱門推薦

本文由 yidianzixun 提供 原文連結

寵物協尋 相信 終究能找到回家的路
寫了7763篇文章,獲得2次喜歡
留言回覆
回覆
精彩推薦