JVM筆記【1】– 運行時數據區
(一)java記憶體區域管理
C/C++每一個new操作都需要自己去delete/free,而java裡面有虛擬機自動管理記憶體,不容易出現記憶體泄漏或者溢出的問題,但是不容易出現不代表不出現,了解虛擬機怎麼使用和管理記憶體是十分重要的是,對程式優化或者問題排查有幫助。
運行時區域主要分為:
- 執行緒私有:
- 程式計數器:
Program Count Register
,執行緒私有,沒有垃圾回收 - 虛擬機棧:
VM Stack
,執行緒私有,沒有垃圾回收 - 本地方法棧:
Native Method Stack
,執行緒私有,沒有垃圾回收
- 程式計數器:
- 執行緒共享:
- 方法區:
Method Area
,以HotSpot
為例,JDK1.8
後元空間取代方法區,有垃圾回收。 - 堆:
Heap
,垃圾回收最重要的地方。
- 方法區:
1.1 程式計數器
空間很小,當前執行緒執行的位元組碼的行號指示器(執行緒獨有,指示當前執行到哪,下一步需要執行哪一個位元組碼),分支,循環,跳轉,異常處理,執行緒恢復都需要依賴它。
執行緒私有:java
多執行緒其實是執行緒輪流切換並分配處理器執行時間的方式實現,一個核一個具體的時間點,只會執行一個執行緒的指令。執行緒切換需要保存和恢復正確的執行位置(保護和恢復現場),所以不同的執行緒需要不同的程式計數器。
- 執行
java
方法時,程式計數器記錄的是正在執行的位元組碼指令地址 - 執行
Native
方法,程式計數器為空
唯一一個沒有規定任何OutOfMemory
的區域,也沒有GC(垃圾回收)。
1.2 虛擬機棧
執行緒私有,生命周期和執行緒一樣,主要是記錄該執行緒Java方法執行的記憶體模型。虛擬機棧裡面放著好多棧幀。注意虛擬機棧,對應是Java方法,不包括本地方法。
一個Java方法執行會創建一個棧幀,一個棧幀主要存儲:
- 局部變數表
- 操作數棧
- 動態鏈接
- 方法出口
每一個方法調用的時候,就相當於將一個棧幀放到虛擬機棧中(入棧),方法執行完成的時候,就是對應著將該棧幀從虛擬機棧中彈出(出棧)。
每一個執行緒有一個自己的虛擬機棧,這樣就不會混起來,如果不是執行緒獨立的話,會造成調用混亂。
大家平時說的java記憶體分為堆和棧,其實就是為了簡便的不太嚴謹的說法,他們說的棧一般是指虛擬機棧,或者虛擬機棧裡面的局部變數表。
局部變數表一般存放著以下數據:
- 基本數據類型(
boolean
,byte
,char
,short
,int
,float
,long
,double
) - 對象引用(reference類型,不一定是對象本身,可能是一個對象起始地址的引用指針,或者一個代表對象的句柄,或者與對象相關的位置)
- returAddress(指向了一條位元組碼指令的地址)
局部變數表記憶體大小編譯期間確定,運行期間不會變化。空間衡量我們叫Slot(局部變數空間)。64位的long和double會佔用2個Slot,其他的數據類型佔用1個Slot。
異常:
- StackOverflowError:執行緒請求的棧深度大於虛擬機允許的深度
- OutOfMemoryError:記憶體不足
1.3 本地方法棧
和虛擬機棧類似,對應本地方法,Native
,虛擬機規範允許語言,使用方式和數據結構不同,有些可能將虛擬機棧和本地方法棧合併。
異常與虛擬機棧一致:
- StackOverflowError:執行緒請求的棧深度大於虛擬機允許的深度
- OutOfMemoryError:記憶體不足
1.4 java堆
堆是記憶體管理最大的一塊,執行緒共享。
虛擬機規範中說,所有的對象實例和數組都要在堆上分配。但是實際上不是所有的對象都在堆上分配,這個和JIT編譯器的發展和逃逸分析技術相關。Why?
// TODO
堆的細分:新生代,老年代,再細分有Eden,From survivor,To survivor等。
堆中也有可能有執行緒私有的區域,分配緩衝區。
物理上可以不連續,但是邏輯上是連續的。
異常:
- OutOfMemoryError:記憶體不足
1.5 方法區
名為非堆,但是實際和堆一樣,是執行緒共享的區域,主要存貯以下資訊:
- 已被虛擬機載入的類資訊
- 常量
- 靜態變數
- 即時編譯器編譯後的程式碼
方法區不等於永久代,指示Hotspot虛擬機將GC分代收集拓展到方法區,也就是用永久代實現了方法區,而其他的虛擬機不一定,不是固定的。JDK1.7將永久代的字元串常量移出了。
方法區回收垃圾的效果不是很好,可以選擇不回收,虛擬機可以決定,當然也可能發生記憶體泄漏。
異常:
- OutOfMemoryError:記憶體分配異常
1.5.1 運行時常量池
運行時常量池時方法區的一部分,但是不是全部,Class
文件主要包括:
- 類的版本
- 欄位
- 方法
- 介面
- 常量池,存放編譯產生的字面量和符號引用,一般除了描述Class文件的符號引用,還有直接引用也在裡面。是動態的,運行時可以產生,比如String.intern()方法。
異常:
- OutOfMemoryError:記憶體分配異常
(二)直接記憶體
不是虛擬機運行時數據區,也不是規範規定的區域,但是使用頻繁且可能會有OutOfMemoryError:記憶體分配異常出現。
比如,NIO(1.4)基於Channel與Buffer的I/O,可以用Native函數直接分配堆外記憶體,通過存儲在Java堆中的DirectByteBuffer對象作為引用來操作,提高性能,不需要Java堆和Native堆都來回複製數據。
直接記憶體受物理的記憶體,或者處理器定址空間之類的限制。
本文系JVM學習相關筆記,整理來自周志明老師的《深入理解Java虛擬機》,無比欽佩,強烈推薦!
【作者簡介】:
秦懷,公眾號【秦懷雜貨店】作者,技術之路不在一時,山高水長,縱使緩慢,馳而不息。這個世界希望一切都很快,更快,但是我希望自己能走好每一步,寫好每一篇文章,期待和你們一起交流。
此文章僅代表自己(本菜鳥)學習積累記錄,或者學習筆記,如有侵權,請聯繫作者核實刪除。人無完人,文章也一樣,文筆稚嫩,在下不才,勿噴,如果有錯誤之處,還望指出,感激不盡~