JVM(4)—執行時資料區
JVM(4)—執行時資料區
執行時資料區的組成
- 執行緒私有
- 程式計數器:儲存執行緒的執行位置。
- 虛擬機器棧:儲存Java方法呼叫與執行過程的資料。
- 本地方法棧:儲存本地方法的執行資料。
- 執行緒共享
- 堆:主要儲存物件。
- 方法區:儲存描述類/方法/欄位等定義資料。
- 執行時常量區:儲存常量資料。
程式計數器
-
記錄當前執行緒所即將執行的位元組碼指令行號(即將要執行哪一行位元組碼指令)。
-
每一個執行緒擁有自己的計數器。
-
執行native本地方法時,程式計數器值為空。
-
佔用記憶體極少,不會出現OOM(Out Of Memory Error)。
-
多執行緒時,CPU時間片切換後,程式計數器可以告訴CPU當前程式執行到了什麼位置。
虛擬機器棧
-
每一個棧幀對應了一個方法,每次方法呼叫都會產生對應的棧幀。
-
虛擬機器棧生命週期與執行緒相同,所有方法執行完後,虛擬機器棧就被銷燬了,執行緒也結束了。
-
執行緒私有。
-
不會被垃圾回收。
-
棧深度(棧幀的數量,3個棧幀棧深度就是3)有限制。1.5後預設每一個執行緒的棧空間為1mb,之前256k。-Xss數值決定了棧的記憶體大小,決定棧的最大深度。
-
棧設定了固定大小(推薦)可能會發生 StackOverflowError:達到上限。
棧可以動態擴充套件可能會發生 OutOfMemoryError:可用記憶體不足。
棧幀的組成
- 區域性變量表:區域性變數
- 運算元棧:儲存中間計算的臨時結果
- 動態連結:將符號引用轉為直接引用
- 返回地址:存放呼叫方法的程式計數器的值
區域性變量表
-
按定義順序儲存方法的引數與方法內的區域性變數。
-
執行緒私有,方法呼叫被建立,方法退出時銷燬。
-
編譯期間長度已經確定,區域性變數元資料儲存在位元組碼中。
-
是棧幀中最主要的儲存空間,決定了棧的深度。
-
儲存區域性變數的單位為Slot(變數槽)。構造方法和例項方法中,0號槽位預設是this,指向當前類的例項。靜態方法中沒有this關鍵字。
-
32位以內的型別(int/float/char/引用型別...)佔1個Slot,64位型別(long/double)佔2個Slot。
(Start PC:位元組碼指令起始行號,給區域性變數分配槽。Length:位元組碼指令作用了x行,釋放槽)
- 槽複用:將原本空閒出來的槽複用給新來的區域性變數,來縮小區域性變量表的尺寸。
運算元棧
位元組碼指令在執行過程中的中間計算結果儲存在運算元棧中。
push壓入運算元棧、store_1將運算元棧棧頂數值存入區域性變量表的1號槽中、load_1讀取區域性變量表1號槽中的值壓入運算元棧、add將運算元棧棧頂與第二個的值取出相加放入運算元棧棧頂、return將運算元棧棧頂數值取出返回。
64位資料型別佔2個運算元棧空間。
動態連結
將儲存在位元組碼中的符號引用轉為執行時記憶體的直接引用。記憶體指標,指向方法區中方法的元資料。
方法返回地址
儲存該方法呼叫者的程式計數器的值,用於方法執行後後續執行。
本地方法棧
native本地方法
native本地方法就是Java呼叫非Java程式碼的介面。
定義native本地方法時,不需要提供方法的實現。
native本地方法可以呼叫其他語言介面實現對作業系統更底層的操作。
native本地方法棧
HotSpot將本地方法棧與虛擬機器棧合二為一。
堆
- 存放執行時例項化的物件例項。new 出來的物件。
- JVM啟動時就建立,堆記憶體也被分配。是JVM記憶體主要佔用區域。
- 執行緒共享。堆記憶體在物理上可以分散,在邏輯上連續。
- 堆中包含執行緒私有的緩衝區(TLAB)用於提高JVM的併發處理效率。
JDK1.8 堆結構
- 新生代:用來存放新生的物件,該區域物件會被頻繁GC。
- 老年代:新生代中的穩定物件放入老年代,該區域不會特別頻繁GC。
- 元空間:記憶體的永久儲存區域,主要存放類、方法的描述資訊(元資料),幾乎不GC。
設定堆的引數
- 設定堆空間大小引數
-
-Xms 設定堆空間(新生代+老年代)的初始記憶體大小。
-
-Xmx 設定堆空間(新生代+老年代)的最大記憶體大小。
- 預設堆空間大小
-
初始記憶體大小 :物理電腦記憶體大小 / 64
-
最大記憶體大小 :物理電腦記憶體大小 / 4
- 新生代與老年代的佔用比例
-
新生代佔用1/3記憶體。
-
老年代佔用2/3記憶體。
- 手動設定堆空間
- -Xms1g -Xmx1g
- 建議將初始堆記憶體與最大堆記憶體設為相同的值。
- 建議Xms與Xmx的值為老年代FillGC後存活物件的3-4倍。
- -XX:+PrintGCDetails
物件分配
- 絕大數剛建立的物件會存入新生代的Eden伊甸園區。
- Eden伊甸園區與S0/S1的記憶體比例為 8:1:1 。
- Eden滿時,會對年輕代進行垃圾回收,稱為MinorGC。MinorGC的執行過程(複製交換演算法):
- 掃描Eden、From、To區域所有物件。
- 在Eden區中如果物件可達(有引用),就給此物件添加個age=1的屬性,將此物件複製到To區。
- 在From區中如果物件可達,就給此物件age的值加1,將此物件複製到To區。
- 清空Eden區與From區所有物件,From區變To區,To區變From區。
- 當n次MinorGC後,物件的age超過了15(閾值),物件會從From區晉升到老年區。
MinorGC/FullGC/MajorGC
- 任何GC都會出發STW(Stop The World,全域性停頓)。
- MinorGC只針對年輕代回收,執行效率高。
- FullGC,全堆回收,針對年輕代、老年代、方法區全面收集,執行效率低下,會導致系統長時間停滯,減少FullGC次數是JVM優化重點。
- MajorGC,只針對老年代回收,CMS垃圾收集器才會存在MajorGC。
GC日誌分析
[GC (Allocation Failure) [PSYoungGen: 8192K->1000K(9216K)] 8192K->6792K(60416K), 0.0027928 secs] [Times: user=0.06 sys=0.03, real=0.00 secs]
// GC : 不加型別預設為MinorGC
// (Allocation Failure) : 產生GC的原因,Eden空間分配不足
// PSYoungGen : GC的範圍是年輕代
// 8192K->1000K(9216K) : GC前年輕代佔用的空間->GC後年輕代佔用的空間(年輕代總空間),MinorGC後只有To區域有物件。
// 8192K->6792K(60416K) : GC前堆佔用的空間->GC後堆佔用的空間(堆總空間)
// 0.0027928 secs : 本次GC使用時間
為什麼年輕代總空間是9216K,而不是10240K?
答:年輕代組成是1個Eden和2個Survivor,只一個Survivor存放物件,所以總空間為Eden空間8192+1個Survivor空間1024=9216.
方法區
- 執行緒共享,物理上可以分散儲存,邏輯為整體的記憶體區域。
- 儲存了類載入資訊、類資訊(包含欄位、方法)、即時編譯器編譯後的程式碼快取、常量、靜態變數(1.6版本以前)
- 方法區只是個概念,具體實現看JVM的不同的廠商。HotSpot的方法區實現為永久代(1.7版本),元空間(1.8版本)。
永久代與元空間的區別
- 規範說明方法區是堆的邏輯部分,但實現與堆記憶體無關,稱為“非堆”。
- 永久代是jdk7和之前版本的方法區實現。
- 特點:佔用JVM記憶體儲存資料,-XX:MaxPermSize有上限,載入的類太多會容易記憶體溢位,與其他JVM實現不一致。
- 元空間是jdk8之後的方法區實現。
- 特點:使用本地記憶體儲存資料,最大為可用實體記憶體,與其他JVM實現一致。
設定元空間
- -XX:MetaspaceSize=xxx
設定元空間初始大小,預設大小與平臺相關,到達進行調整並自動觸發FullGC。
建議調整為一個較大的值,減少FullGC的可能。
- -XX:MaxMetaspaceSize=xxx
設定元空間最大尺寸,預設為-1,代表最大可用記憶體,一般不設定最大值。
- 超過最大值後會出現OOM:Metaspace
執行時常量區
將位元組碼檔案中的常量池存放到執行時常量池中。
方法區的歷史變化