JVM總結以內存區域

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

程序計數器

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

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

執行Java方法的時候,這個計數器記錄的是正在執行的虛擬機字節碼的指令的地址;執行native方法的時候,計數器的值爲空(null)。線程

Java虛擬機棧

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

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

棧幀對象

在活動線程中,只有虛擬機棧頂的棧幀纔是有效的,稱爲當前棧幀(Current Stack Frame),這個棧幀所關聯的方法稱爲當前方法(Current Method)。執行引用所運行的全部字節碼指令都只針對當前棧幀進行操做。棧幀的概念結構以下圖所示:blog

  • 局部變量表:局部變量表是一組變量值存儲空間,用於存放方法參數和方法內部定義的局部變量。在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虛擬機》

Java永久代去哪兒了

相關文章
相關標籤/搜索