JVM核心技術(第一篇)

女友在高考發表於2021-05-15

Java基礎知識

java是一個物件導向的,靜態型別,編譯執行,有VM/GC和執行時的跨平臺的高階語言。

一. 位元組碼技術

將寫好的java檔案編譯成class

javac .\TestJvm.java

檢視位元組碼

javap -c TestJVM

檢視更詳細的位元組碼

javap -c -verbose TestJVM

位元組碼的執行時結構

JVM是一個基於棧的計算機器。每個執行緒都有他所對應的執行緒棧,用於儲存棧幀。每一次方法呼叫,都會建立一個棧幀。

棧幀由運算元棧,區域性變數陣列以及一個Class引用(也叫動態連結)組成。

  • 運算元棧:每個幀都包含了一個後入先出的棧,稱為運算元棧。

  • 區域性變數表:用於存放方法引數和內部定義的區域性變數。區域性變數表的容量以變數槽(Slot)為單位,一個Slot只能存放一個boolean、byte、char、shoert、int、float、reference或returnAddress型別的資料

  • Class引用:指向當前方法在執行時常量池中對應的class

二、JVM類載入器

類的生命週期

  1. 載入:找class檔案
  2. 校驗 :驗證格式,依賴
  3. 準備 :靜態欄位,方法表
  4. 解析:符號解析為引用
  5. 初始化 :構造器,靜態變數賦值,靜態程式碼塊
  6. 使用
  7. 解除安裝
類的載入時機
  1. 當虛擬機器啟動時,初始化使用者指定的主類,就是啟動執行的 main 方法所在的類;
  2. 當遇到用以新建目標類例項的 new 指令時,初始化 new 指令的目標類,就是 new
    一個類的時候要初始化;
  3. 當遇到呼叫靜態方法的指令時,初始化該靜態方法所在的類;
  4. 當遇到訪問靜態欄位的指令時,初始化該靜態欄位所在的類
  5. 子類的初始化會觸發父類的初始化
  6. 如果一個介面定義了 default 方法,那麼直接實現或者間接實現該介面的類的初始化,
    會觸發該介面的初始化
  7. 使用反射 API 對某個類進行反射呼叫時,初始化這個類,其實跟前面一樣,反射呼叫
    要麼是已經有例項了,要麼是靜態方法,都需要初始化
  8. 當初次呼叫 MethodHandle 例項時,初始化該 MethodHandle 指向的方法所在的

不會初始化(可能會載入)

  1. 通過子類引用父類的靜態欄位,只會觸發父類的初始化,而不會觸發子類的初始化
  2. 定義物件陣列,不會觸發該類的初始化
  3. 常量在編譯期間會存入呼叫類的常量池中,本質上並沒有直接引用定義常量的類,不會觸發定義常量所在的類
  4. 通過類名獲取 Class 物件,不會觸發類的初始化,Hello.class 不會讓 Hello 類初始化。
  5. 通過 Class.forName 載入指定類時,如果指定引數 initialize 為 false 時,也不會觸
    發類初始化,其實這個引數是告訴虛擬機器,是否要對類進行初始化。Class.forName
    (“jvm.Hello”)預設會載入 Hello 類。
  6. 通過 ClassLoader 預設的 loadClass 方法,也不會觸發初始化動作(載入了,但是
    不初始化)

三類類載入器

從上到下依次是:

  • 啟動類載入器(BootstrapClassLoader)
  • 擴充套件類載入器(ExtClassLoader)
  • 應用類載入器(AppClassLoader)

類載入器特點:
雙親委派、負責依賴、快取載入

載入過程:如一個Hello.class檔案,不考慮自定義載入器,首先會在AppClassLoader中檢查是否已經載入過,如果載入過就不載入了。如果沒有載入過,就會拿到父載入器,那麼父載入器(ExtClassLoader)就會檢查是否載入過,如果沒有,就再往上,讓BootStrapClassLoader檢查是否載入過。

如果還是沒有,因為他的上面已經沒有父載入器了,那麼他就開始自己載入,如果能載入,他就自己載入。不能載入,就下沉到子載入器去載入,一直到最底層,如果沒有類載入器能載入就丟擲異常ClassNotFoundException

新增引用類的幾種方式

  1. 放到JDK的lib/ext下,或者-Djava.ext.dirs
  2. java -cp/classpath 或者將class檔案放在當前路徑
  3. 自定義ClassLoader載入
  4. 拿到當前執行類的ClassLoader,反射呼叫addUrl方法新增jar或路徑
public class Test1 {

    public static void main(String[] args) throws MalformedURLException, NoSuchMethodException, ClassNotFoundException, InvocationTargetException, IllegalAccessException {
       String appurl="file:/d:/logs/";
        URLClassLoader classLoader = (URLClassLoader)Test1.class.getClassLoader();
        Method addURL = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
        addURL.setAccessible(true);
        URL url=new URL(appurl);
        addURL.invoke(classLoader,url);
        Class.forName("Test2");
    }
}

我將Test2.class檔案放在d盤的logs資料夾下。

三、JVM記憶體結構

Jvm整體結構

可以看到,我們的JVM程式裡面除了堆還有棧、非堆、JVM自身。而我們的作業系統裡還有其他程式。

所以我們設定堆記憶體的時候,不能設定為機器的記憶體大小,如4G的機器千萬不能把-Xms -Xmx 設定為4G,一般設定為機器記憶體的60%-70%。

JVM棧結構

棧:執行緒棧,也叫Java方法棧,每啟動一個執行緒就會建立一個棧,如果使用了JNI方法,就會分配一個單獨的本地方法棧。執行緒執行過程中,一般會有多個方法組成呼叫關係,如方法A呼叫方法B,每執行到一個方法,就會建立一個棧幀。

所有的原生物件型別(如int,long)和物件引用地址都在棧上儲存。

JVM堆結構

堆:物件、物件成員以及類定義、靜態變數都在堆上。

什麼是JMM?

Java記憶體模型。明確定義了不同的執行緒之間,通過哪些方式,在什麼時候可以看到其他執行緒儲存在共享變數中的值;以及如何對共享變數進行同步。JMM規範的是執行緒間的互動操作。

從抽象上來看,JMM定義了執行緒和主記憶體之間的抽象關係。

四、JVM啟動引數

JVM啟動引數有如下幾類:

  • 以-開頭為標準引數,所有的 JVM 都要實現這些引數,並且向後相容。如:++-server++

  • 以-D開頭的,設定系統屬性 如:++-Dfile.encoding=UTF-8++

  • 以 -X 開頭為非標準引數, 基本都是傳給 JVM 的,
    預設 JVM 實現這些引數的功能,但是並不保證所
    有 JVM 實現都滿足,且不保證向後相容。 可以使
    用 java -X 命令來檢視當前 JVM 支援的非標準參 如:++-Xmx8g++
    數。

  • 以 –XX:開頭為非穩定引數, 專門用於控制 JVM
    的行為,跟具體的 JVM 實現有關,隨時可能會在
    下個版本取消。

  • -XX:+-Flags 形式, +- 是對布林值進行開關 如:++-XX:+UseG1GC++

  • -XX:key=value 形式, 指定某個選項的值 如:++-XX:MaxPermSize=256m++

4.1 系統屬性引數

-Dfile.encoding=UTF-8 -Duser.timezone=GMT+08

或者通過
System.setProperty("a","A100");設定,Linux上還可以通過a=A100 java XXX 設定。

4.2 執行模式
  1. -server:設定 JVM 使用 server 模式,特點是啟動速度比較慢,但執行時效能和記憶體管理效率
    很高,適用於生產環境。在具有 64 位能力的 JDK 環境下將預設啟用該模式,而忽略 -client 參
    數。
  2. -client :JDK1.7 之前在32位的 x86 機器上的預設值是 -client 選項。設定 JVM 使用 client 模
    式,特點是啟動速度比較快,但執行時效能和記憶體管理效率不高,通常用於客戶端應用程式或
    者 PC 應用開發和除錯。此外,我們知道 JVM 載入位元組碼後,可以解釋執行,也可以編譯成本
    地程式碼再執行,所以可以配置 JVM 對位元組碼的處理模式:
  3. -Xint:在解釋模式(interpreted mode)下執行,-Xint 標記會強制 JVM 解釋執行所有的位元組
    碼,這當然會降低執行速度,通常低10倍或更多。
  4. -Xcomp:-Xcomp 引數與-Xint 正好相反,JVM 在第一次使用時會把所有的位元組碼編譯成本地
    程式碼,從而帶來最大程度的優化。【注意預熱】
  5. -Xmixed:-Xmixed 是混合模式,將解釋模式和編譯模式進行混合使用,有 JVM 自己決定,這
    是 JVM 的預設模式,也是推薦模式。 我們使用 java -version 可以看到 mixed mode 等資訊。
4.3 堆記憶體

-Xmx, 指定最大堆記憶體。 如 -Xmx4g. 這只是限制了 Heap 部分的最大值為4g。
這個記憶體不包括棧記憶體,也不包括堆外使用的記憶體。

-Xms, 指定堆記憶體空間的初始大小。 如 -Xms4g。 而且指定的記憶體大小,並
不是作業系統實際分配的初始值,而是GC先規劃好,用到才分配。 專用伺服器上需要保持 –Xms 和 –Xmx 一致,否則應用剛啟動可能就有好幾個 FullGC。
當兩者配置不一致時,堆記憶體擴容可能會導致效能抖動。

-Xmn, 等價於 -XX:NewSize,使用 G1 垃圾收集器 不應該設定該選項,在其他的某些業務場景下可以設定。官方建議設定為 -Xmx 的 1/2 ~ 1/4.

-XX:MaxPermSize=size, 這是 JDK1.7 之前使用的。Java8 預設允許的Meta空間無限大,此引數無效。

-XX:MaxMetaspaceSize=size, Java8 預設不限制 Meta 空間, 一般不允許設定該選項。

-XX:MaxDirectMemorySize=size,系統可以使用的最大堆外記憶體,這個引數跟 -Dsun.nio.MaxDirectMemorySize 效果相同。

-Xss, 設定每個執行緒棧的位元組數。 例如 -Xss1m指定執行緒棧為1MB,與-XX:ThreadStackSize=1m 等價

4.4 GC相關

-XX:+UseG1GC:使用 G1 垃圾回收器

-XX:+UseConcMarkSweepGC:使用 CMS 垃圾回收器

-XX:+UseSerialGC:使用序列垃圾回收器

-XX:+UseParallelGC:使用並行垃圾回收器

// Java 11+
-XX:+UnlockExperimentalVMOptions -XX:+UseZGC

// Java 12+
-XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC

4.5 分析診斷

-XX:+-HeapDumpOnOutOfMemoryError 選項, 當 OutOfMemoryError 產生,即記憶體溢位(堆記憶體或持久代)時自動 Dump 堆記憶體。

示例用法: java -XX:+HeapDumpOnOutOfMemoryError -Xmx256m ConsumeHeap

-XX:HeapDumpPath 選項, 與 HeapDumpOnOutOfMemoryError 搭配使用, 指定記憶體溢位時 Dump 檔案的目錄。
如果沒有指定則預設為啟動 Java 程式的工作目錄。

示例用法: java -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/local/ ConsumeHeap

自動 Dump 的 hprof 檔案會儲存到 /usr/local/ 目錄下。

-XX:OnError 選項, 發生致命錯誤時(fatal error)執行的指令碼。
例如, 寫一個指令碼來記錄出錯時間, 執行一些命令, 或者 curl 一下某個線上報警的 url.

示例用法:java -XX:OnError="gdb - %p" MyApp
可以發現有一個 %p 的格式化字串,表示程式 PID。

-XX:OnOutOfMemoryError 選項, 丟擲 OutOfMemoryError 錯誤時執行的指令碼。

-XX:ErrorFile=filename 選項, 致命錯誤的日誌檔名,絕對路徑或者相對路徑。

-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=1506,遠端除錯

4.6 JavaAgent

Agent 是 JVM 中的一項黑科技, 可以通過無侵入方式來做很多事情,比如注入 AOP 程式碼,執行統
計等等,許可權非常大。這裡簡單介紹一下配置選項,詳細功能需要專門來講。

設定 agent 的語法如下:

-agentlib:libname[=options] 啟用 native 方式的 agent, 參考 LD_LIBRARY_PATH 路徑。

-agentpath:pathname[=options] 啟用 native 方式的 agent。

-javaagent:jarpath[=options] 啟用外部的 agent 庫, 比如 pinpoint.jar 等等。

-Xnoagent 則是禁用所有 agent。

以下示例開啟CPU使用時間抽樣分析:JAVA_OPTS="-agentlib:hprof=cpu=samples,file=cpu.samples.log

相關文章