天天看點

JVM 學習筆記

JVM 學習筆記(基于JDK11)

類加載過程

JVM 學習筆記
  1. 加載(Loading):Loading 階段讀取類檔案産生二進制流,并轉化為特定的資料結構,初步校驗 cafe babe 魔法數、常量池、檔案長度、是否有父類等,然後建立對應類的 java.lang.Class 對象執行個體。
  2. 驗證(Verification):驗證是更詳細的校驗,比如 final 是否合規、類型是否正确、靜态變量是否合理等
  3. 準備(Preparation):準備階段是為靜态變量配置設定記憶體,并設定預設值,
  4. 解析(Resolution):解析類和方法確定類與類之間的互相引用正确性,完成記憶體結構布局。
  5. 初始化(Initialization):Init 階段執行類構造器方法,如果指派運算是通過其他類的靜态方法來完成的,那麼會馬上解析另外個類,在虛拟機槍中執行完畢後通過傳回值進行指派。
  6. 使用(Using):
  7. 解除安裝(Unloading):

注意以下幾種情況不會執行類初始化:

  1. 通過子類引用父類的靜态字段,隻會觸發父類的初始化,而不會觸發子類的初始化。
  2. 定義對象數組,不會觸發該類的初始化。
  3. 常量在編譯期間會存入調用類的常量池中,本質上并沒有直接引用定義常量的類,不會觸發定義常量所在的類。
  4. 通過類名擷取Class對象,不會觸發類的初始化。
  5. 通過Class.forName加載指定類時,如果指定參數initialize為false時,也不會觸發類初始化,其實這個參數是告訴虛拟機,是否要對類進行初始化。
  6. 通過ClassLoader預設的loadClass方法,也不會觸發初始化動作。

雙親委派模型

JVM 學習筆記

當一個類收到了類加載請求,他首先不會嘗試自己去加載這個類,而是把這個請求委派給父類去完成,每一個層次類加載器都是如此,是以所有的加載請求都應該傳送到啟動類加載其中,隻有當父類加載器回報自己無法完成這個請求的時候(在它的加載路徑下沒有找到所需加載的Class),子類加載器才會嘗試自己去加載。 采用雙親委派的一個好處是比如加載位于 rt.jar 包中的類 java.lang.Object,不管是哪個加載器加載這個類,最終都是委托給頂層的啟動類加載器進行加載,這樣就保證了使用不同的類加載器最終得到的都是同樣一個Object對象。

自定義類加載器

  1. 繼承 ClassLoader 類;
  2. 重寫 findClass() 方法;
  3. 調用 defineClass() 方法。

JVM 記憶體布局

JVM 學習筆記

堆區(Heap)

注意:JDK8 的字元串常量移植到堆記憶體中

設定堆區初始化參數:

-Xms256M -Xmx1024M
其中 -X 表示是 JVM 運作參數
ms 是 memory start 的簡稱,代表最小堆容量
mx 是 memory max 的簡稱,代表最大堆容量
但是在通常情況下,服務在運作過程中,堆空間不斷擴大與縮小,勢必會形成不必要的系統壓力,是以線上上生成環境中,JVM 的 Xms 和 Xmx 設定成一樣的大小,避免在 GC 後調整堆大小是帶來的額外壓力。      

堆分成兩大塊,新生代(Young)和老年代(Old)。

對象産生之初在新生代,步入暮年時進入老年代,但是老年代也接納在新生代無法容納的超大對象。新生代=1個Eden區+2個Survivor區。絕大部分對象在Eden區生成,當Eden區裝填滿的時候,會觸發 Young Garbage Collection,即 YGC。垃圾回收的時候,在Eden區實作清除政策,沒有被引用的對象則直接回收。依然存活的對象會被移送到Survivor區,這個區真是名副其實的存在。Survivor區分為so和Sl兩塊記憶體空間,送到哪塊空間呢?每次YGC的時候,它們将存活的對象複制到未使用的那塊空間,然後将目前正在使用的空間完全清除,交換兩塊空間的使用狀态。如果YGC要移送的對象大于Survivor區容量的上限,則直接移交給老年代。每個對象都有一個計數器,每次YGC都會加l。-XX:MaxTenuringThreshold參數能配置計數器的值到達某個闡值的時候,對象從新生代晉升至老年代。預設值是15,可以在Survivor區交換14次之後,晉升至老年代。如圖4-9所示。

如果 Survivor 區無法放下,或者超大對象的鬧值超過上限,則嘗試在老年代中進行配置設定;如果老年代也無法放下,則會觸發Full Garbage Collection,即FGC 。如果依然無法放下,則抛出OOM。堆記憶體出現OOM 的機率是所有記憶體耗盡異常中最高的。出錯時的堆内資訊對解決問題非常有幫助, 是以給 JVM 設定運作參數 -XX:+HeapDumpOnOutOfMemoryError ,讓 JVM 遇到 OOM 異常時能輸出堆内資訊,特别是對相隔數月才出現的 OOM 異常尤為重要。

JVM 學習筆記

元空間(Metaspace)

在JDK8 裡, Perm 區(永久的)中的所有内容中字元串常量移至堆記憶體,其他内容包括類元資訊、字段、靜态屬性、方法、常量等都移動至元空間内,

JVM 虛拟機棧(JVM Stack)

虛拟機棧通過壓棧和出棧的方式,對每個方法對應的活動棧幀進行運算處理,方法正常執行結束,肯定會跳轉到另一個棧幀上。在執行的過程中,如果出現異常,會進行異常回溯,傳回位址通過異常處理表确定。棧幀在整個JVM 體系中的地位頗高,包括局部變量表、操作棧、動态連接配接、方法傳回位址等。

局部變量表

局部變量表是存放方法參數和局部變量的區域。

操作棧

操作棧是一個初始狀态為空的桶式結構棧。在方法執行過程中,會有各種指令往棧中寫人和提取資訊。JVM 的執行引擎是基于棧的執行引擎,其中的棧指的就是操作棧。位元組碼指令集的定義都是基于棧類型的,棧的深度在方法元資訊的stack 屬性中。

動态連接配接

每個棧幀中包含一個在常量池中對目前方法的引用, 目的是支援方法調用過程的動态連接配接。

方法傳回位址

方法執行時有兩種退出情況:正常退出,即正常執行到任何方法的傳回位元組碼指令,如RETURN 、IRETURN 、ARETURN 等,第二, 異常退出。無論何種退出情況,都将傳回至方法目前被調用的位置。方法退出的過程相當于彈出目前棧幀,退出可能有三種方式

  • 傳回值壓入上層調用枝幀。
  • 異常資訊抛給能夠處理的槍幀。
  • PC 計數器指向方法調用後的下一條指令。

本地方法棧(Native Method Stacks)

本地方法棧(Native Method Stack)在JVM 記憶體布局中,也是線程對象私有的,但是虛拟機棧“主内”, 而本地方法棧“主外”。這個“内外”是針對JVM 來說的,本地方法棧為 Native 方法服務。線程開始調用本地方法時,會進入一個不再受JVM限制的世界。本地方法可以通過JNI ( Java Native Interface )來通路虛拟機運作時的資料區,甚至可以調用寄存器,具有和 JVM 相同的能力和權限。當大量本地方法出現時, 勢必會削弱 JVM 對系統的控制力,因為它的出錯資訊都比較黑盒。對于記憶體不足的情況, 本地方法棧還是會抛出native heap OutOfMemory 。

JNI 類本地方法, 最著名的本地方法應該是 System.currentTimeMillis()

程式計數寄存器(Program Counter Pegister)

在程式計數寄存器( Program Counter Register, PC )中, Register 的命名源于CPU 的寄存器, CPU 隻有把資料裝載到寄存器才能夠運作。寄存器存儲指令相關的現場資訊,由于CPU 時間片輪限制,衆多線程在并發執行過程中,任何一個确定的時刻,一個處理器或者多核處理器中的一個核心,隻會執行某個線程中的一條指令。這樣必然導緻經常中斷或恢複,如何保證分毫無差呢?每個線程在建立後,都會産生自己的程式計數器和棧幀,程式計數器用來存放執行指令的偏移量和行号訓示器等,線程執行或恢複都要依賴程式計數器。程式計數器在各個線程之間互不影響,此區域也不會發生記憶體溢出異常。

JVM 記憶體區域

JVM 學習筆記

GC 參數

參考連結:

https://blog.csdn.net/SIMBA1949/article/details/99930215

引用類型

強引用

在 Java 中最常見的就是強引用,把一個對象賦給一個引用變量,這個引用變量就是一個強引用。當一個對象被強引用變量引用時,它處于可達狀态,它是不可能被垃圾回收機制回收的,即使該對象以後永遠都不會被用到JVM 也不會回收。是以強引用是造成Java記憶體洩漏的主要原因之一。

軟引用

軟引用需要用 SoftReference 類來實作,對于隻有軟引用的對象來說,當系統記憶體足夠時它不會被回收,當系統記憶體空間不足時它會被回收。軟引用通常用在對記憶體敏感的程式中。

弱引用

弱引用需要用 WeakReference 類來實作,它比軟引用的生存期更短,對于隻有弱引用的對象來說,隻要垃圾回收機制一運作,不管JVM的記憶體空間是否足夠,總會回收該對象占用的記憶體。

虛引用

虛引用需要 PhantomReference 類來實作,它不能單獨使用,必須和引用隊列聯合使用。虛引用的主要作用是跟蹤對象被垃圾回收的狀态。

垃圾收集算法

引用計數法

标記清除法

标記壓縮算法

複制算法

分代算法

GC 垃圾收集器

  1. Serial 垃圾收集器(單線程、複制算法)
  2. ParNew 垃圾收集器(Serial+多線程)
  3. Parallel Scavenge 收集器(多線程複制算法、高效)
  4. Serial Old 收集器(單線程标記整理算法 )
  5. Parallel Old 收集器(多線程标記整理算法)
  6. CMS 收集器(多線程标記清除算法)
  7. G1 收集器