JVM記憶體的那些事
前言
對於C語言開發的程式設計師來說,在記憶體管理方面,必須負責每一個物件的生命週期,從有到無。
對於Java程式設計師你來說,在虛擬機器記憶體管理的幫助下,不需要為每個new物件都匹配free操作,記憶體洩露和記憶體溢位等問題也不太容易出現,不過也正是因為把記憶體管理交給了虛擬機器,一旦執行中的程式出現了記憶體洩露問題,給排查過程造成很大困難。所以只有理解了Java虛擬機器的執行機制,才能夠運籌帷幄於各種程式碼。本文以HotSpot為例說說虛擬機器的那些事。
JAVA虛擬機器把管理的記憶體劃分為幾個不同的資料區。
Java堆
Java堆是被所有執行緒共享的一塊記憶體區域,主要用於存放物件例項,Java虛擬機器規範中有這樣一段描述:所有的物件例項和資料都要在堆上進行分配。為物件分配記憶體就是把一塊大小確定的記憶體從堆記憶體中劃分出來,通常有兩種方法實現:
1 、指標碰撞法
假設Java堆中記憶體時完整的,已分配的記憶體和空閒記憶體分別在不同的一側,通過一個指標作為分界點,需要分配記憶體時,僅僅需要把指標往空閒的一端移動與物件大小相等的距離。
2、空閒列表法
事實上,Java堆的記憶體並不是完整的,已分配的記憶體和空閒記憶體相互交錯,JVM通過維護一個列表,記錄可用的記憶體塊資訊,當分配操作發生時,從列表中找到一個足夠大的記憶體塊分配給物件例項,並更新列表上的記錄。
物件建立是一個非常頻繁的行為,進行堆記憶體分配時還需要考慮多執行緒併發問題,可能出現正在給物件A分配記憶體,指標或記錄還未更新,物件B又同時分配到原來的記憶體,解決這個問題有兩種方案:
1、採用CAS保證資料更新操作的原子性;
2、把記憶體分配的行為按照執行緒進行劃分,在不同的空間中進行,每個執行緒在Java堆中預先分配一個記憶體塊,稱為本地執行緒分配緩衝(Thread Local Allocation Buffer, TLAB);
Java棧
Java棧是執行緒私有的,每個執行緒對應一個Java棧,每個執行緒在執行一個方法時會建立一個對應的棧幀(Stack Frame),棧幀負責儲存區域性變數變數表、運算元棧、動態連結和方法返回地址等資訊。每個方法的呼叫過程,相當於棧幀在Java棧的入棧和出棧過程。
區域性變數表 用於存放方法引數和方法內部定義的區域性變數,其大小在程式碼編譯期間已經確定,在方法執行期間不會改變。區域性變數表以變數槽(Slot)為最小儲存單位,每個Slot能夠存放一個boolean、byte、char、shot、int、float、reference和returnAddress型別的32位資料,對於64位的資料型別long和double,虛擬機器會以高位對齊的方式為其分配兩個連續的Slot空間。
在方法執行時,如果是例項方法,即非static方法,區域性變數表中第0位Slot預設存放物件例項的引用,在方法中可以通過關鍵字 this 進行訪問,方法引數按照引數列表順序,從第1位Slot開始分配,方法內部變數則按照定義順序進行分配其餘的Slot。
class test { public int calc(int a, int b, String operation) { operation = "+"; return a + b; } public void main(String args[]) { calc(100, 200, "+"); } }
對應的區域性變數表如下:
使用 javap -c 命令檢視方法calc的位元組碼
其中iload_1和iload_2分別從區域性變數表中的第1位和第2位中載入資料。
方法區
方法區和Java堆一樣,是所有執行緒共享的記憶體區域,用於存放已被虛擬機器載入的類資訊、常量、靜態變數和即時編譯器編譯後的程式碼等資料。
執行時常量池是方法區的一部分,用於存放編譯期間生成的各種字面常量和符號引用。
指令計數器
指令計數器是執行緒私有的,每個執行緒都有獨立的指令計數器,計數器記錄著虛擬機器正在執行的位元組碼指令的地址,分支、迴圈、跳轉、異常處理和執行緒恢復等操作都依賴這個計數器完成。如果執行緒執行的是native方法,這個計數器則為空。
物件的記憶體佈局
物件在記憶體中佈局可以分成三塊區域:物件頭、例項資料和對齊填充。
1、物件頭
物件頭包括兩部分資訊:執行時資料和型別指標,如果物件是一個陣列,還需要一塊用於記錄陣列長度的資料。
1.1、執行時資料包括雜湊碼(HashCode)、GC分代年齡、鎖狀態標誌、執行緒持有的鎖、偏向鎖ID和偏向時間戳等,這部分資料在32位和64位虛擬機器中的長度分別為32bit和64bit,官方稱為”Mark Word”。Mark Word被設計成非固定的資料結構,以實現在有限空間內儲存儘可能多的資料。
32位的虛擬機器中,物件未被鎖定的狀態下,Mark Word的32bit中25bit儲存物件的HashCode、4bit儲存物件分代年齡、2bit儲存鎖標誌位、1bit固定為0,具體如下:
其它狀態(輕量級鎖定、重量級鎖定、GC鎖定、可偏向鎖)下Mark Word的儲存內容如下:
1.2、物件頭的型別指標指向該物件的類後設資料,虛擬機器通過這個指標可以確定該物件是哪個類的例項。
2、例項資料
例項資料就是在程式程式碼中所定義的各種型別的欄位,包括從父類繼承的,這部分的儲存順序會受到虛擬機器分配策略和欄位在原始碼中定義順序的影響。
3、對齊填充
由於HotSpot的自動記憶體管理要求物件的起始地址必須是8位元組的整數倍,即物件的大小必須是8位元組的整數倍,物件頭的資料正好是8的整數倍,所以當例項資料不夠8位元組整數倍時,需要通過對齊填充進行補全。
相關文章
- JVM 記憶體的那些事JVM記憶體
- JVM 記憶體模型 記憶體分配,JVM鎖JVM記憶體模型
- Java的記憶體 -JVM 記憶體管理Java記憶體JVM
- JVM記憶體JVM記憶體
- iOS開發那些事-響應記憶體警告iOS記憶體
- iOS記憶體管理的那些事兒-原理及實現iOS記憶體
- JVM記憶體分析JVM記憶體
- JVM記憶體系列JVM記憶體
- jvm記憶體區JVM記憶體
- JVM記憶體分配JVM記憶體
- JVM記憶體模型JVM記憶體模型
- JVM的記憶體區域JVM記憶體
- JVM(七):JVM記憶體結構JVM記憶體
- JVM-記憶體模型JVM記憶體模型
- JVM記憶體-GC策略JVM記憶體GC
- JVM記憶體模型(五)JVM記憶體模型
- 【JVM記憶體區域】JVM記憶體
- HotSpot JVM 記憶體管理HotSpotJVM記憶體
- JVM-記憶體管理JVM記憶體
- JVM 記憶體區域JVM記憶體
- JVM記憶體區域JVM記憶體
- java jvm 記憶體分配JavaJVM記憶體
- JVM記憶體劃分JVM記憶體
- JVM記憶體結構JVM記憶體
- NIO的JVM記憶體和機器記憶體的選擇JVM記憶體
- JVM——記憶體洩漏與記憶體溢位JVM記憶體溢位
- JVM系列(二) - JVM記憶體區域JVM記憶體
- jvm系列(二):JVM記憶體結構JVM記憶體
- 【JVM】JVM系列之記憶體模型(六)JVM記憶體模型
- JVM記憶體GC的騙局JVM記憶體GC
- JVM -- 物件的記憶體佈局JVM物件記憶體
- 遺失的JVM堆記憶體JVM記憶體
- 再看JVM:垃圾回收那些事JVM
- JVM記憶體分為3個記憶體空間JVM記憶體
- GC那些事兒–Android記憶體優化第一彈GCAndroid記憶體優化
- 聊聊記憶體那些事(基於微控制器系統)記憶體
- GC那些事兒--Android記憶體優化第一彈GCAndroid記憶體優化
- 深入理解JVM(一)——JVM記憶體模型JVM記憶體模型