Java虛擬機運行時數據區

Java虛擬機運行時數據區

Java 虛擬機在執行Java程序過程中會把它管理的內存劃分為若干個不同的數據區域。這些區域有著各自的用途,以及創建和銷毀時間,有的區域隨著虛擬機進程的啟動而存在,有些區域則依賴用戶線程的啟動和結束建立和銷毀。

Java虛擬機運行時內存模型
  • 線程私有:程序計數器,本地方法棧,虛擬機棧

  • 線程隔離:方法區,堆區

線程私有數據區

程序計數器

程序計數器是一塊較小的內存空間,它的作用可以看作是當前線程所執行的字節碼的行號指示器,字節碼解釋器工作時通過改變該計數器的值來選擇下一條需要執行的字節碼指令,分支、跳轉、循環等基礎功能都要依賴它來實現。

由于Java虛擬機的多線程是通過線程輪流切換并分配處理器執行時間的方式來實現的,在任何一個時刻,一個處理器只會執行一條線程中的指令。為了線程切換后能恢復到正確的位置,每條線程都有一個獨立的的程序計數器,各線程間的計數器互不影響,獨立存儲,因此該區域是線程私有的。

當線程在執行一個 Java 方法時,該計數器記錄的是正在執行的虛擬機字節碼指令的地址,當線程在執行的是 Native 方法(調用本地操作系統方法)時,該計數器的值為空。另外,該內存區域是唯一一個在 Java 虛擬機規范中沒有規定任何 OOM(內存溢出:OutOfMemoryError)情況的區域。

Java 虛擬機棧

Java虛擬機棧 是Java方法執行的內存模型,是線程私有的。每個方法在執行的時候都會創建一個棧幀,用于存儲局部變量表、操作數棧、動態鏈接、方法出口等信息,而且 每個方法從調用直至完成的過程,對應一個棧幀在虛擬機棧中入棧到出棧的過程。 對于執行引擎來講,活動線程中,只有棧頂的棧幀是有效的,稱為當前棧幀,這個棧幀所關聯的方法稱為當前方法,執行引擎所運行的所有字節碼指令都只針對當前棧幀進行操作。

棧幀結構

在 Java 虛擬機規范中,對這個區域規定了兩種異常情況:

  • 如果線程請求的棧深度大于虛擬機所允許的深度,將拋出StackOverflowError異常。

  • 如果虛擬機在動態擴展棧時無法申請到足夠的內存空間,則拋出OutOfMemoryError異常。

每個線程擁有一個自己的棧,這個棧的大小決定了方法調用的可達深度(遞歸多少層次,或嵌套調用多少層其他方法,-Xss 參數可以設置虛擬機棧大小),若線程請求的棧深度大于虛擬機允許的深度,則拋出 StackOverFlowError 異常。此外,棧的大小可以是固定的,也可以是動態擴展的,若虛擬機棧可以動態擴展(大多數虛擬機都可以),但擴展時無法申請到足夠的內存(比如沒有足夠的內存為一個新創建的線程分配棧空間時),則拋出 OutofMemoryError 異常。

局部變量

局部變量表主要存放一些基本數據類型(int, short, long, byte, float, double, boolean, char), 對象引用(reference類型,它不等同于對象本身,可能是一個指向對象起始地址的引用指針,或者代表對象的句柄)和return Adreess 類型(指向了一條字節碼指令的地址)。局部變量表所需的內存空間在編譯期間完成分配,即在 Java 程序被編譯成 Class 文件時,就確定了所需分配的最大局部變量表的容量。當進入一個方法時,這個方法需要在棧中分配多大的局部變量空間是完全確定的,在方法運行期間不會改變局部變量表的大小。

局部變量表的容量以變量槽(Slot)為最小單位。在虛擬機規范中并沒有明確指明一個 Slot 應占用的內存空間大小(允許其隨著處理器、操作系統或虛擬機的不同而發生變化),一個 Slot 可以存放一個32位以內的數據類型:boolean、byte、char、short、int、float、reference 和 returnAddresss。reference 是對象的引用類型,returnAddress 是為字節指令服務的,它執行了一條字節碼指令的地址。對于 64 位的數據類型(long和double),虛擬機會以高位在前的方式為其分配兩個連續的 Slot 空間。

操作數棧

操作數棧又常被稱為操作棧,操作數棧的最大深度也是在編譯的時候就確定了。32 位數據類型所占的棧容量為 1,64 位數據類型所占的棧容量為 2。當一個方法開始執行時,它的操作棧是空的,在方法的執行過程中,會有各種字節碼指令(比如:加操作、賦值元算等)向操作棧中寫入和提取內容,也就是入棧和出棧操作。

Java 虛擬機的解釋執行引擎稱為“基于棧的執行引擎”,其中所指的“棧”就是操作數棧。因此我們也稱 Java 虛擬機是基于棧的,這點不同于 Android 虛擬機,Android 虛擬機是基于寄存器的。

基于棧的指令集最主要的優點是可移植性強,主要的缺點是執行速度相對會慢些;而由于寄存器由硬件直接提供,所以基于寄存器指令集最主要的優點是執行速度快,主要的缺點是可移植性差。

動態連接

每個棧幀都包含一個指向運行時常量池(在方法區中,后面介紹)中該棧幀所屬方法的引用,持有這個引用是為了支持方法調用過程中的動態連接。Class 文件的常量池中存在有大量的符號引用,字節碼中的方法調用指令就以常量池中指向方法的符號引用為參數。這些符號引用,一部分會在類加載階段或第一次使用的時候轉化為直接引用(如 final、static 域等),稱為靜態解析,另一部分將在每一次的運行期間轉化為直接引用,這部分稱為動態連接。

方法返回地址

當一個方法被執行后,有兩種方式退出該方法:執行引擎遇到了任意一個方法返回的字節碼指令或遇到了異常,并且該異常沒有在方法體內得到處理。無論采用何種退出方式,在方法退出之后,都需要返回到方法被調用的位置,程序才能繼續執行。方法返回時可能需要在棧幀中保存一些信息,用來幫助恢復它的上層方法的執行狀態。一般來說,方法正常退出時,調用者的 PC 計數器的值就可以作為返回地址,棧幀中很可能保存了這個計數器值,而方法異常退出時,返回地址是要通過異常處理器來確定的,棧幀中一般不會保存這部分信息。

方法退出的過程實際上等同于把當前棧幀出站,因此退出時可能執行的操作有:恢復上層方法的局部變量表和操作數棧,如果有返回值,則把它壓入調用者棧幀的操作數棧中,調整 PC 計數器的值以指向方法調用指令后面的一條指令。

本地方法棧

該區域與虛擬機棧所發揮的作用非常相似,只是虛擬機棧為虛擬機執行 Java 方法服務,而本地方法棧則為使用到的本地操作系統(Native)方法服務。

線程共享數據區

堆(GC堆)

Java Heap 是 Java 虛擬機所管理的內存中最大的一塊,它是所有線程共享的一塊內存區域,在虛擬機啟動時創建。此內存區域的唯一目的就是存放對象實例,幾乎所有的對象實例和數組都在這類分配內存。Java Heap 是垃圾收集器管理的主要區域,因此很多時候也被稱為“GC堆”。

從內存回收的角度看,由于現在收集器基本是采用分代收集算法,所以Java 堆可以細分為:新生代(Eden,),老年代

堆結構

分配緩沖區(Thread Local Allocation Buffer ,TLAB

Sun Hotspot JVM 為了提升對象內存分配的效率,對于所創建的線程都會分配一塊獨立的空間 TLAB,其大小由JVM根據運行的情況計算而得。在TLAB上分配對象時不需要加鎖(相對于CAS配上失敗重試方式 ),因此JVM在給線程的對象分配內存時會盡量的在TLAB上分配,在這種情況下JVM中分配對象內存的性能和C基本是一樣高效的,但如果對象過大的話則仍然是直接使用分配到老年代。

根據 Java 虛擬機規范的規定,Java 堆可以處在物理上不連續的內存空間中,只要邏輯上是連續的即可。在實現時,既可以實現成固定大小的,也可以是可擴展的,如果在堆中沒有內存可分配時,并且堆也無法擴展時,將會拋出 OutOfMemoryError 異常。

方法區

方法區 與Java堆一樣,是各個線程共享的內存區域,而且不需要連續的內存和可以選擇固定大小或者可擴展的。它用與存儲已被虛擬機加載的類信息常量靜態變量即時編譯器編譯后的代碼等數據。 雖然方法句被Java虛擬機規范描述為堆的一個邏輯部分,但是他卻有一個別名叫Non-Heap(非堆) 。根據 Java 虛擬機規范的規定,當方法區無法滿足內存分配需求時,將拋出 OutOfMemoryError 異常 。

運行時常量池

常量池(Class文件常量池),用于存放編譯器生成的各種字面量和符號引用,這部分內容將在類加載后存放到方法區的運行時常量池中

運行時常量池相對于 Class 文件常量池的另一個重要特征是具備動態性,Java 語言并不要求常量一定只能在編譯期產生,也就是并非預置入 Class 文件中的常量池的內容才能進入方法區的運行時常量池,運行期間也可能將新的常量放入池中,這種特性被開發人員利用比較多的是 String 類的 intern()方法。同樣作為方法區的一部分,當常量池無法申請到內存時會拋出OutOfMemoryError 異常 。

方法區的回收

相對而言,垃圾收集行為在方法區是比較少出現的。這個區域回收的主要目標廢棄常量和無用的類。

回收廢棄常量

與Java堆中的對象非常類似。 假如常量池中的字符串"abc",沒有被任何String對象,也沒有其他地方引用,如果發生垃圾回收,"abc"就會被回收。常量池中其他類(接口),方法,字段的符號引用類似。

回收無用的類

判定一個常量是否是“廢棄常量”比較簡單,而要判定一個類是否是“無用的類”的條件則相對苛刻許多。類需要同時滿足下面3個條件才能算是“無用的類”:

  • 該類所有的實例都已經被回收,也就是Java堆中不存在該類的任何實例;

  • 加載該類的ClassLoader已經被回收;

  • 該類對應的 java.lang.Class 對象沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法。

虛擬機可以對滿足上述3個條件的無用類進行回收(卸載),這里說的僅僅是“可以”,而不是和對象一樣,不使用了就必然會回收。

在大量使用反射、動態代理、CGLib等bytecode框架的場景,以及動態生成JSP和OSGi這類頻繁自定義ClassLoader的場景都需要虛擬機具備類卸載的功能,以保證永久代不會溢出

直接內存

直接內存并不是虛擬機運行時數據區的一部分,也不是Java虛擬久規范中定義的內存區域,由于頻繁的內使用,且可能導致OutOfMemoryError異常。

在 JDK1.4 中新引入了 NIO 機制,它是一種基于通道與緩沖區的新 I/O 方式,可以直接從操作系統中分配直接內存,即在堆外分配內存,這樣能在一些場景中提高性能,因為避免了在 Java 堆和 Native 堆中來回復制數據。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,885評論 6 541
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,312評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,993評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,667評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,410評論 6 411
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,778評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,775評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,955評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,521評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,266評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,468評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,998評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,696評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,095評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,385評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,193評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,431評論 2 378

推薦閱讀更多精彩內容