1. 程式人生 > 實用技巧 >JVM(4)—執行時資料區

JVM(4)—執行時資料區

JVM(4)—執行時資料區

執行時資料區的組成

  • 執行緒私有
  1. 程式計數器:儲存執行緒的執行位置。
  2. 虛擬機器棧:儲存Java方法呼叫與執行過程的資料。
  3. 本地方法棧:儲存本地方法的執行資料。
  • 執行緒共享
  1. 堆:主要儲存物件。
  2. 方法區:儲存描述類/方法/欄位等定義資料。
  3. 執行時常量區:儲存常量資料。

程式計數器

  1. 記錄當前執行緒所即將執行的位元組碼指令行號(即將要執行哪一行位元組碼指令)。

  2. 每一個執行緒擁有自己的計數器。

  3. 執行native本地方法時,程式計數器值為空。

  4. 佔用記憶體極少,不會出現OOM(Out Of Memory Error)。

  5. 多執行緒時,CPU時間片切換後,程式計數器可以告訴CPU當前程式執行到了什麼位置。

虛擬機器棧

  1. 每一個棧幀對應了一個方法,每次方法呼叫都會產生對應的棧幀。

  2. 虛擬機器棧生命週期與執行緒相同,所有方法執行完後,虛擬機器棧就被銷燬了,執行緒也結束了。

  3. 執行緒私有。

  4. 不會被垃圾回收。

  5. 棧深度(棧幀的數量,3個棧幀棧深度就是3)有限制。1.5後預設每一個執行緒的棧空間為1mb,之前256k。-Xss數值決定了棧的記憶體大小,決定棧的最大深度。

  6. 棧設定了固定大小(推薦)可能會發生 StackOverflowError:達到上限。

    棧可以動態擴充套件可能會發生 OutOfMemoryError:可用記憶體不足。

棧幀的組成

  • 區域性變量表:區域性變數
  • 運算元棧:儲存中間計算的臨時結果
  • 動態連結:將符號引用轉為直接引用
  • 返回地址:存放呼叫方法的程式計數器的值

區域性變量表

  1. 按定義順序儲存方法的引數方法內的區域性變數

  2. 執行緒私有,方法呼叫被建立,方法退出時銷燬。

  3. 編譯期間長度已經確定,區域性變數元資料儲存在位元組碼中。

  4. 是棧幀中最主要的儲存空間,決定了棧的深度。

  5. 儲存區域性變數的單位為Slot(變數槽)。構造方法和例項方法中,0號槽位預設是this,指向當前類的例項。靜態方法中沒有this關鍵字。

  6. 32位以內的型別(int/float/char/引用型別...)佔1個Slot,64位型別(long/double)佔2個Slot。

(Start PC:位元組碼指令起始行號,給區域性變數分配槽。Length:位元組碼指令作用了x行,釋放槽)

  1. 槽複用:將原本空閒出來的槽複用給新來的區域性變數,來縮小區域性變量表的尺寸。

運算元棧

位元組碼指令在執行過程中的中間計算結果儲存在運算元棧中。

push壓入運算元棧、store_1將運算元棧棧頂數值存入區域性變量表的1號槽中、load_1讀取區域性變量表1號槽中的值壓入運算元棧、add將運算元棧棧頂與第二個的值取出相加放入運算元棧棧頂、return將運算元棧棧頂數值取出返回。

64位資料型別佔2個運算元棧空間。

動態連結

將儲存在位元組碼中的符號引用轉為執行時記憶體的直接引用。記憶體指標,指向方法區中方法的元資料。

方法返回地址

儲存該方法呼叫者的程式計數器的值,用於方法執行後後續執行。

本地方法棧

native本地方法

native本地方法就是Java呼叫非Java程式碼的介面。

定義native本地方法時,不需要提供方法的實現。

native本地方法可以呼叫其他語言介面實現對作業系統更底層的操作。

native本地方法棧

HotSpot將本地方法棧與虛擬機器棧合二為一。

  1. 存放執行時例項化的物件例項。new 出來的物件。
  2. JVM啟動時就建立,堆記憶體也被分配。是JVM記憶體主要佔用區域。
  3. 執行緒共享。堆記憶體在物理上可以分散,在邏輯上連續。
  4. 堆中包含執行緒私有的緩衝區(TLAB)用於提高JVM的併發處理效率。

JDK1.8 堆結構

  1. 新生代:用來存放新生的物件,該區域物件會被頻繁GC。
  2. 老年代:新生代中的穩定物件放入老年代,該區域不會特別頻繁GC。
  3. 元空間:記憶體的永久儲存區域,主要存放類、方法的描述資訊(元資料),幾乎不GC。

設定堆的引數

  1. 設定堆空間大小引數
  • -Xms 設定堆空間(新生代+老年代)的初始記憶體大小。

  • -Xmx 設定堆空間(新生代+老年代)的最大記憶體大小。

  1. 預設堆空間大小
  • 初始記憶體大小 :物理電腦記憶體大小 / 64

  • 最大記憶體大小 :物理電腦記憶體大小 / 4

  1. 新生代與老年代的佔用比例
  • 新生代佔用1/3記憶體。

  • 老年代佔用2/3記憶體。

  1. 手動設定堆空間
  • -Xms1g -Xmx1g
  • 建議將初始堆記憶體與最大堆記憶體設為相同的值。
  • 建議Xms與Xmx的值為老年代FillGC後存活物件的3-4倍。
  • -XX:+PrintGCDetails

物件分配

  1. 絕大數剛建立的物件會存入新生代的Eden伊甸園區。
  2. Eden伊甸園區與S0/S1的記憶體比例為 8:1: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

  1. 任何GC都會出發STW(Stop The World,全域性停頓)。
  2. MinorGC只針對年輕代回收,執行效率高。
  3. FullGC,全堆回收,針對年輕代、老年代、方法區全面收集,執行效率低下,會導致系統長時間停滯,減少FullGC次數是JVM優化重點。
  4. 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. 執行緒共享,物理上可以分散儲存,邏輯為整體的記憶體區域。
  2. 儲存了類載入資訊、類資訊(包含欄位、方法)、即時編譯器編譯後的程式碼快取、常量、靜態變數(1.6版本以前)
  3. 方法區只是個概念,具體實現看JVM的不同的廠商。HotSpot的方法區實現為永久代(1.7版本),元空間(1.8版本)。

永久代與元空間的區別

  1. 規範說明方法區是堆的邏輯部分,但實現與堆記憶體無關,稱為“非堆”。
  2. 永久代是jdk7和之前版本的方法區實現。
    • 特點:佔用JVM記憶體儲存資料,-XX:MaxPermSize有上限,載入的類太多會容易記憶體溢位,與其他JVM實現不一致。
  3. 元空間是jdk8之後的方法區實現。
    • 特點:使用本地記憶體儲存資料,最大為可用實體記憶體,與其他JVM實現一致。

設定元空間

  • -XX:MetaspaceSize=xxx

設定元空間初始大小,預設大小與平臺相關,到達進行調整並自動觸發FullGC。

建議調整為一個較大的值,減少FullGC的可能。

  • -XX:MaxMetaspaceSize=xxx

設定元空間最大尺寸,預設為-1,代表最大可用記憶體,一般不設定最大值。

  • 超過最大值後會出現OOM:Metaspace

執行時常量區

將位元組碼檔案中的常量池存放到執行時常量池中。

方法區的歷史變化