jvm-第五節類加載器

# jvm-第五節類加載器

本篇知識點概況

  1. 字節碼相關知識
    1. 字節碼的基礎知識,瞭解字節碼的概念,用途,特點
    2. 字節碼的分析工具使用
    3. class文件格式格式與詳解
    4. 字節碼的指令:基礎存儲運算指令,異常處理,裝箱拆箱,數組
    5. 熱點探測之jit與字節碼
  2. 類加載與類加載器相關知識
    1. 一個類的生命週期
    2. jdk提供的三種類加載器
    3. 自定義類加載器
  3. 問題:

字節碼相關知識

1.字節碼的基礎知識,瞭解字節碼的概念,用途,特點

  1. 字節碼是一堆指令的集合,它具有平臺無關性,可以運行在任何支持jvm的平臺上;是Java程序在運行時的執行單元;它是一種中間形式的代碼,是源代碼和機器碼之間的橋樑1688627785662

2.字節碼的分析工具使用

  1. 以這個工具舉例,可以查看具體的指令,可以修改裏面的值,github地址https://github.com/ingokegel/jclasslib
  2. 168862825335516886282832271688628293720

3.class文件格式格式與詳解

  1. 1688628472727
  2. class文件中各個部分的含義
    1. class文件裏是16進制的(一個字節佔8位,一個十六進制佔4位,所以cafebabe佔32位),前四個字節0xCAFEBABE;作用是標識這是一個有效的class文件;
    2. 後面的 0000 0034表示版本;其中第五六個字節表示次版本號,第七八字節是主版本號(0034(16)=52(10));Java 版本從45開始jdk1.1每發行一個大版本就加一
    3. 常量池 0010 表示常量的數量,常量池中常見的倆類常量,字面量以及符號引用,字面量一般是靜態常量,符號引用是類的全限定名字段名,方法名;1688631567456
    4. 訪問標誌(Access Flags):(瞭解)
      • 訪問標誌指示了類或者接口的訪問權限和屬性,如是否是public、final、abstract等。
      • 訪問標誌由兩個字節表示,使用特定的標誌位來表示不同的訪問屬性。
    5. 類索引、父類索引和接口索引(This Class, Super Class, Interfaces):(瞭解)
      • 類索引指向當前類在常量池中的類描述符的常量項。
      • 父類索引指向當前類的直接父類在常量池中的類描述符的常量項。
      • 接口索引表存儲了當前類所實現的接口的索引,每個接口索引指向常量池中的接口描述符的常量項。
    6. 字段表(Fields):(瞭解)
      • 字段表用於描述類或接口中定義的字段信息,包括字段的訪問標誌、名稱、描述符等。
      • 字段表中的每一項都包含了字段的相關信息,可以是類變量、實例變量或常量。
    7. 方法表(Methods):(瞭解)
      • 方法表用於描述類或接口中定義的方法信息,包括方法的訪問標誌、名稱、描述符等。
      • 方法表中的每一項都包含了方法的相關信息,包括方法的參數列表、返回值類型、異常表等。
    8. 屬性表(Attributes):(瞭解)
      • 屬性表用於存儲與類、字段或方法相關的附加信息,如註解、源文件信息、行號表等。
      • 屬性表包含了屬性的名稱、長度以及具體的屬性值。

4.字節碼的指令:基礎存儲運算指令,異常處理,裝箱拆箱,數組(瞭解)

  1. 基礎存儲運算指令:包括將值加載到操作數棧上、從操作數棧上存儲值到變量中等操作。例如:
    • iload:將int類型的變量加載到操作數棧上
    • istore:將int類型的值存儲到變量中
  2. 異常處理指令:用於處理異常情況,包括拋出異常和捕獲異常。例如:
    • athrow:拋出異常
    • try-catch:捕獲和處理異常
  3. 裝箱拆箱指令:用於基本類型和對應的包裝類之間的轉換。例如:
    • invokestatic:調用靜態方法進行裝箱或拆箱
    • invokevirtual:調用包裝類的實例方法進行裝箱或拆箱
  4. 數組指令:用於創建和操作數組。例如:
    • newarray:創建一個基本類型的數組
    • anewarray:創建一個引用類型的數組
    • arraylength:獲取數組的長度
    • iaload:將int類型的值加載到操作數棧上

5.熱點探測之jit與字節碼

  1. java程序運行時主要就是執行字節碼指令,解釋執行時需要翻譯成機器碼,這個效率比較低,爲了提高效率就有了jit(just in time compiler);

  2. 熱點代碼,比如for裏的代碼就會緩存起來,爲了下次用

  3. 熱點探測:在 HotSpot 虛擬機中的熱點探測是 JIT 優化的條件,熱點探測是基於計數器的熱點探測,採用這種方法的虛擬機會爲每個方法建立計數器統計方法的執

    行次數,如果執行次數超過一定的閾值就認爲它是“熱點方法”

    虛擬機爲每個方法準備了兩類計數器:方法調用計數器(

    Invocation Counter)和回邊計數器(Back Edge Counter)。在確定虛擬機運行參數的前提下,這

    兩個計數器都有一個確定的閾值,當計數器超過閾值溢出了,就會觸發 JIT 編譯。

  4. 方法調用計數器:用於統計方法被調用的次數,方法調用計數器的默認閾值在 C1 模式下是 1500 次,在 C2 模式在是 10000 次,可通過 -XX: CompileThreshold 來設定;

    而在分層編譯的情況下,-XX: CompileThreshold 指定的閾值將失效,此時將會根據當前待編譯的方法數以及編譯線程數來動態調整。當方法計數器和回邊

    計數器之和超過方法計數器閾值時,就會觸發 JIT 編譯器。

  5. 回邊計數器:用於統計一個方法中循環體代碼執行的次數,在字節碼中遇到控制流向後跳轉的指令稱爲“回邊”(Back Edge),該值用於計算是否觸發 C1 編譯的閾值,

    在不開啓分層編譯的情況下,C1 默認爲 13995,C2 默認爲 10700,可通過 -XX: OnStackReplacePercentage=N 來設置;而在分層編譯的情況下,-XX:

    OnStackReplacePercentage 指定的閾值同樣會失效,此時將根據當前待編譯的方法數以及編譯線程數來動態調整。

    建立回邊計數器的主要目的是爲了觸發 OSR(On StackReplacement)編譯,即棧上編譯。在一些循環週期比較長的代碼段中,當循環達到回邊計數器閾值時,JVM 會認爲這段是熱點代碼,JIT 編譯器就會將這段代碼編譯成機器語言並緩存,在該循環時間段內,會直接將執行代碼替換,執行緩存的機器語

    言。

6.字節碼總結:都是一些需要了解的東西,在以後jvm調優,會用到

類加載與類加載器相關知識

1.一個類的生命週期

  1. 一個類的生命週期:加載,驗證,準備,解析,初始化,使用,卸載1688632254083
  2. 這裏有個順序問題,解析的順序可能在初始化之後纔開始,爲了支持Java的動態綁定;
  3. 加載的時機:
    1. 初次使用時
    2. 引用了類中的某個靜態屬性
    3. 反射調用
    4. 子類繼承時調用
  4. 驗證:包括四種驗證,文件格式,元數據,字節碼,符號引用驗證,驗證重要但不是必要步驟,如果覺得沒必要可以通過參數關閉-Xverify:none
  5. 準備:爲類中的static修飾的屬性賦初始值,這是賦值表1688632819181
  6. 解析:將符號引用替換成直接引用的過程
  7. 初始化:
    1. 當遇到四個關鍵字 new getstatic putstatic 和invokestatic時就會觸發初始化
    2. 觸發反射時
    3. 觸發子類時,父類要初始化
    4. 當虛擬機啓動時,用戶需要指定一個要執行的主類(包含 main()方法的那個類),虛擬機會先初始化這個主類
    5. 下面的案例子類調用父類的屬性時,子類和父類的加載情況和初始化情況,這塊建議看一下字節碼,看他是否加載了子類
      1. 1688633713359
      2. 1688633729843
  8. 線程安全:由於一個類的初始化時一個一個線程來的,其他線程都阻塞,所以是線程安全的

2.jdk提供的三層類加載器

  1. bootstrap classloader:加載核心類庫,任何加載行爲都要經過他,c++編寫,隨着jvm啓動;
  2. extention classloader:主要加載lib/ext 下的jar和class文件,這是一個java類繼承了 urlClassLoader
  3. application classloader:是默認的Java類加載器,加載classPath下的jar class文件,我們寫的代碼首先用這個加載器
  4. custom classloader:自定義加載器,支持一些擴展,下面詳細說
  5. 類加載器問題;對於任意一個類,同一個類加載器,同一個類,確定jvm中該類的唯一性,注意每一個類加載器有自己獨立的命名空間
  6. 雙親委派機制:向上委託,向下加載,可以避免重複加載一個類1688635119950

3.自定義類加載器tomcat

  1. 先說結論, 如何解決tomcat通過war發佈服務違背雙親委派機制的問題?
    1. 將第三方依賴放入tomcat的公共目錄,由公共加載器加載就實現了共享,使用webappclassloader加載器加在web就先實現了每個web有自己獨立的類加載器,實現了隔離;
  2. 爲什麼說tomcat通過war發佈服務是違背了雙親委派機制,因爲tomcat有一個webappclassloader,擁有加載web的優先權,這意味着當他需要加載類時會首先搜索自己的路徑( 即WEB-INF/classesWEB-INF/lib目錄 );
  3. 1688635294764
  4. 如果一個jvm運行着倆個不同版本的web,是如何解決的呢?看下面的代碼
  5. 1688636829991
  6. 1688636845752

自定義類加載器-擴展

  1. spi:service provider interface,是一套被第三方實現,擴展的api,他不是在編譯時檢查,而是在運行時加載,表現爲當我們寫一行代碼 class.forName("com.mysql.jdbc.Driver"),不會報錯 ,詳細的解釋如下

    1. 在Java的SPI(Service Provider Interface)機制中,Class.forName("com.mysql.jdbc.Driver")並不需要引入對應的JAR文件來讓代碼編譯通過。這是因爲在SPI機制中,服務提供者的具體實現類是通過類路徑(Classpath)動態加載的,而不是在編譯時就確定的。

      當你調用Class.forName("com.mysql.jdbc.Driver")時,JVM會嘗試在類路徑上查找並加載com.mysql.jdbc.Driver類。如果找到了該類,JVM就會加載它並執行相應的靜態代碼塊。這時,com.mysql.jdbc.Driver類會向JVM註冊自己作爲MySQL數據庫的驅動程序。這樣,在後續的代碼中,你就可以使用java.sql.DriverManager類來獲取MySQL數據庫的連接。

      如果你在運行時沒有將MySQL驅動程序的JAR文件放在類路徑上,Class.forName("com.mysql.jdbc.Driver")會拋出ClassNotFoundException異常。但是,如果你確保MySQL驅動程序的JAR文件已經由其他方式加載(如通過Tomcat的公共庫目錄),那麼Class.forName("com.mysql.jdbc.Driver")就不會拋出異常,因爲類加載器已經能夠找到並加載了com.mysql.jdbc.Driver類。

      需要注意的是,最新的MySQL驅動已經遷移到了com.mysql.cj.jdbc.Driver類,而不再是com.mysql.jdbc.Driver。因此,如果你使用的是較新的MySQL驅動版本,應該使用Class.forName("com.mysql.cj.jdbc.Driver")來加載驅動類。

      總結來說,Class.forName("com.mysql.jdbc.Driver")不會在編譯時檢查類的存在與否,而是在運行時動態加載類。如果類路徑上存在對應的類,加載就會成功,否則會拋出ClassNotFoundException異常。

問題

  1. 爲什麼要拆箱裝箱
  2. integerCache
  3. 雙親委派機制的原名: Parent Delegation Mode , 父級委託模型 這個名稱更貼切一些
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章