JVM——內存區(qū)域:運行時數(shù)據(jù)區(qū)域詳解

關注:CodingTechWork,一起學習進步。

引言

??我們經(jīng)常會被問到一個問題是Java和C++有何區(qū)別?我們除了能回答一個是面向對象、一個是面向過程編程以外,我們還會從底層內存管理和垃圾收集方面作出比較。
??對于C++而言,程序員既要做程序設計開發(fā)又要維護底層內存管理;而對于Java而言,程序員不需要控制底層,只需要安心寫自己的代碼即可,因為Java虛擬機自動實現(xiàn)了內存管理以及垃圾回收。
??但是,我們寫的程序或者程序環(huán)境問題等也時長出現(xiàn)內存泄露和溢出,這個時候程序員如果不知道虛擬機如何分配和管理內存,排查問題將舉步維艱。下面我們就來一起看看,Java虛擬機內存是如何劃分的。

運行時數(shù)據(jù)區(qū)

概念

??Java虛擬機在執(zhí)行Java程序時,會將內存區(qū)域劃分為不同的數(shù)據(jù)區(qū)域(運行時數(shù)據(jù)區(qū))。有些數(shù)據(jù)區(qū)域是跟隨VM進程的啟動而存在,有的區(qū)域是跟隨用戶線程的啟動和結束而建立和銷毀。

分類

??一般運行時數(shù)據(jù)區(qū)分為:程序計數(shù)器、虛擬機棧、本地方法棧、Java堆、方法區(qū)、運行時常量池。

結構

Java虛擬機運行時數(shù)據(jù)區(qū)

內存劃分

運行時數(shù)據(jù)區(qū)域大小

參數(shù)說明

程序計數(shù)器

定義

??程序計數(shù)器(Program Counter Register)是一塊較小的內存空間,是當前線程所執(zhí)行的字節(jié)碼的行號指示器。字節(jié)碼解釋器工作時就是通過改變這個計數(shù)器的值來選擇下一條需要執(zhí)行的字節(jié)碼指令(分支、循環(huán)、跳轉、異常處理、線程恢復等功能)

線程私有

??多線程是通過線程輪換交替并分配處理器執(zhí)行時間的方式實現(xiàn),任何時刻,一個處理器都只會執(zhí)行一條線程的指令,每條線程需要一個獨立的程序計數(shù)器,這樣各條線程之間的計數(shù)器互不影響,獨立存儲,從而保證線程切換后可以恢復到正確的執(zhí)行位置。
??反例:線程A和線程B交替執(zhí)行,線程A計數(shù)到5,切換執(zhí)行,線程B計數(shù)到9,如果兩個線程共享程序計數(shù)器,則線程A此時計數(shù)共享線程B的計數(shù)值9,則無法恢復到正常的執(zhí)行位置。
??正例:線程A和線程B交替執(zhí)行,線程A計數(shù)到5,切換執(zhí)行,線程B計數(shù)到9,線程A再切換執(zhí)行時,繼續(xù)計數(shù)到5開始遞增,恢復到正確的執(zhí)行位置。

異常

??此內存區(qū)域無OutOfMemoryError異常。

用途

??若線程正在執(zhí)行一個Java方法,這個計數(shù)器記錄的是正在執(zhí)行的虛擬機字節(jié)碼指令的地址;若線程正在執(zhí)行的Native方法,則計數(shù)器值為空。

Java虛擬機棧

定義

??虛擬機棧(Java Virtual Machine Stacks)是Java方法執(zhí)行的內存模型,每個方法在執(zhí)行的同時都會創(chuàng)建一個棧幀,用于存儲局部變量表、操作數(shù)棧、動態(tài)鏈接、方法出口等信息。每個方法的從調用一直到執(zhí)行完成,都對應著一個棧幀在VM棧中入棧到出棧的過程。

結構

虛擬機棧結構

線程私有

??Java虛擬機棧是線程私有的,其生命周期和線程同步,隨著線程的啟動而創(chuàng)建,隨線程的結束而銷毀。

局部變量表

??Loca Variable Table,虛擬機棧中的局部變量表通常就是所說的虛擬機棧,是一組變量值存儲空間,用于存放方法參數(shù)和方法內部定義的局部變量。其中存放了編譯期間可知的基本數(shù)據(jù)類型、對象引用類型和方法返回地址類型。
??局部變量表所占用的內存空間是在編譯期間就已經(jīng)分配好了,進入一個方法時,這個方法需要在幀中分配多大的局部變量空間是確定的,在方法運行期間也不會去改變這個局部變量表的大小。
??基本數(shù)據(jù)類型:8種基本數(shù)據(jù)類型是boolean、byte 8位、char、short 16位、int 32位、float 32位、long 64位、double 64位。其中64位長度的long和double類型數(shù)據(jù)會占用2個局部變量空間(slot),其余的都是占用1個slot。
??對象引用類型:reference類型,與對象引用不等價,一般是指向對象起始地址的引用指針,或者是指向一個代表對象的句柄、其他與此對象相關的的位置。
??方法返回地址類型:returnAddress類型指向一條字節(jié)碼指令的地址。

操作數(shù)棧

??Operand Stack,操作數(shù)棧也稱為操作棧,是一個后入先出棧,其中村的每個元素可以是任意的Java數(shù)據(jù)類型。當一個方法剛開始執(zhí)行時,該方法的操作數(shù)棧是空的,當方法執(zhí)行過程中,會有各種字節(jié)碼指令往操作數(shù)棧中寫入和提取內容(入棧/出棧操作)

動態(tài)鏈接

??Dynamic Linking,每個棧幀都包含一個指向運行時常量池中該棧幀所屬方法的應用,持有這個引用就是為了支持方法調用過程中的動態(tài)鏈接,常量池中的符號引用在每一次運行期間轉化為直接引用即為動態(tài)鏈接。

方法返回地址

??returnAddress,當一個方法開始執(zhí)行后,會有兩種方式退出該方法:正常完成出口異常完成出口。
??正常完成出口:程序執(zhí)行過程中遇到任意一個方法返回的字節(jié)碼指令,返回值傳遞給上一層調用者,正常退出程序方法。
??異常完成出口:程序方法執(zhí)行過程中遇到異常,這個異常沒有在方法體內得到處理(沒有try...catch,沒有throw異常),導致方法退出。

異常

??StackOverflowError異常:線程請求的棧深度大于虛擬機所允許的深度,拋出該異常。
??OutOfMemoryError異常:虛擬機可以動態(tài)擴展時,不能夠擴展申請到足夠的內存,拋出該異常。

本地方法棧

定義

??本地方法棧(Native Method Stack)是為虛擬機使用的Native方法服務

異常

??StackOverflowError異常:線程請求的棧深度大于虛擬機所允許的深度,拋出該異常。
??OutOfMemoryError異常:虛擬機可以動態(tài)擴展時,不能夠擴展申請到足夠的內存,拋出該異常。

與虛擬機棧異同


??虛擬機棧是為VM執(zhí)行Java方法(字節(jié)碼)服務;本地方法棧是為VM執(zhí)行的Native方法。
:
??與虛擬機作用類似,有的VM是將兩者合二為一,都會拋出StackOverFlowErrorOutOfMemoryError異常。

Java堆

定義

??Java堆(Java Heap)是JVM中最大的一塊內存,是存放對象實例的區(qū)域。所有對象實例以及數(shù)組都要在堆上分配。當然,也有特特殊的情況,JIT編譯器的發(fā)展與逃逸分析技術會促進棧上分配及變量替換優(yōu)化技術的發(fā)展。

結構

Java堆

線程共享

??Java堆是被所有線程共享的一塊內存區(qū)域,在VM啟動時創(chuàng)建。線程共享可以將Java堆劃分出多個線程私有的分配緩沖區(qū)(Thread Local Allocation Buffer,TLAB)。

GC堆

??Garbage Collected Heap,Java堆是垃圾收集器管理的主要區(qū)域,簡稱GC堆。Java堆細分為新生代和老年代,再細分為Eden空間、From Survivor空間、To Survivor空間。

Java堆劃分空間

堆空間

??Java堆可以處于物理上不連續(xù)的內存空間,只需要邏輯連續(xù)即可,有點類似于磁盤空間。實現(xiàn)時,既可以是固定大小,也可以擴展(-Xmx和-Xms參數(shù)進行控制)。

異常

??若堆中沒有內存完成實例分配,堆也無法擴展時,會拋出OutOfMemoryError異常。

方法區(qū)

定義

??方法區(qū)(Method Area)用于存儲已被虛擬機加載的類的信息、常量、靜態(tài)變量、即時編譯器編譯后的代碼等數(shù)據(jù)。

線程共享

??和Java堆一樣,也是線程共享區(qū)域。

持久代

??對于HotSpot虛擬機而言,方法區(qū)可以稱作為“永久代”或者“持久代”,這是因為HotSpot虛擬機的設計團隊選擇把GC分代收集擴展至了方法區(qū),使用永久代來實現(xiàn)方法區(qū),垃圾收集器可以像管理Java堆一樣管理這部分內存區(qū)域。

持久代區(qū)域

限制

??方法區(qū)和Java堆一樣不需要連續(xù)的物理內存空間,可以選擇固定大小也可以選擇擴展,同時,可選擇不實現(xiàn)垃圾收集,方法區(qū)永久代中的數(shù)據(jù)不代表永久存在,該內存區(qū)域的內存回收目標是針對常量池的回收和對類型的卸載。

運行時常量池

??運行時常量池(Runtime Constant Pool)是方法區(qū)的一部分,相比較而言,Class文件中除了有類的版本、字段、方法、接口等描述信息外,還有一項信息就是常量池(Constant Pool Table)Class文件常量池用于存放編譯器生成的各種字面量和符號引用,這部分內容將在類加載后進入方法區(qū)的運行時常量池中存放。
??JVM規(guī)范對運行時常量池限制比較寬松,不同的廠商可以根據(jù)自己的需求自行實現(xiàn)該內存區(qū)域,一般而言,除了保存Class文件中描述的符號引用外,還會將轉化出的直接引用存儲在其中。
??與Class文件常量池相比較而言,運行時常量池還具備動態(tài)性,常量不一定在編譯器產(chǎn)生,即并非預置入Class文件常量池的內容才能進入方法區(qū)運行時常量池,運行期間也可以將新的常量放入池子中,如String類的intern()方法。

異常

??無法申請到內存時,會拋出OutOfMemoryError異常。

補充

直接內存(Direct Memory)

  1. 該區(qū)域不屬于虛擬機運行時數(shù)據(jù)區(qū)的一部分,也不是java虛擬機規(guī)范中定義的內存預期,但會頻繁使用且導致OutOfMemoryError。
  2. JDK1.4中新加入的NIO(New Input/Output)類,引入一種基于通道(Channel)與緩沖區(qū)(Buffer)的I/O方式,可以使用Native函數(shù)庫直接分配堆外內存,然后通過一個存儲在Java堆中的DirectByteBuffer對象作為這塊內存的引用進行操作(可避免在Java堆和Native堆中來回復制數(shù)據(jù)
  3. 不會受到Java堆大小的限制,但受本機總內存大小以及處理器尋址空間的限制??赏ㄟ^-Xmx等參數(shù)來設置實際內存大小,在我們實際操作過程中,經(jīng)常忽視直接內存,使得整個內存區(qū)域總和大于物理內存限制,導致OutOfMemoryError異常。

總結

運行時數(shù)據(jù)區(qū) 線程是否私有 作用 異常
程序計數(shù)器 線程私有 每個線程都有自己的程序計數(shù)器,是當前線程所執(zhí)行的字節(jié)碼的行號指示器。
虛擬機棧 線程私有 是Java方法執(zhí)行的內存模型,每個方法在執(zhí)行的同時都會創(chuàng)建一個棧幀(Stack Frame)用戶存儲局部變量表、操作數(shù)棧、動態(tài)鏈接、方法出口等信息。每個方法從調用直至執(zhí)行完成的過程,就是對應著一個棧幀在虛擬機棧中入棧和出棧的過程。 若線程請求的棧深度大于虛擬機所允許的深度,拋出StackOverflowError異常;若虛擬機可以動態(tài)擴展,而擴展時無法申請到足夠的內存,就拋出OutOfMemoryError異常
本地方法棧 線程私有 為虛擬機使用本地(Native)方法服務 同虛擬機棧
Java堆 線程共享 在虛擬機啟動時創(chuàng)建,該區(qū)域唯一目的是存放對象實例,幾乎所有實例都是在這里分配內存;gc的主要區(qū)域 若堆中沒有內存完成實例分配且堆無法再擴展時,將會拋出OutOfMemoryError異常。
方法區(qū) 線程共享 存儲已被VM加載的類信息、常量、靜態(tài)變量、即時編譯器后的代碼等數(shù)據(jù) 當方法區(qū)無法滿足內存分配需求時,將拋出OutOfMemoryError異常

至此,JVM運行時數(shù)據(jù)區(qū)全部總結完畢。

參考
《深入理解Java虛擬機》

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容