JVM核心技術(第一篇)


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/4 ~ 1/2.

-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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM