JVM類加載機制 --由調用鏈使用字節碼增強技術引出的類加載

  • 類加載的7個階段(生命週期):
    • 加載
      • 生成二進制字節流,將靜態結構轉化成方法去的運行時數據結構
      • 生.class對象,作爲方法區這個類的各種數據的訪問入口
      • JVM ClassLoader 在這個階段
    • 驗證
      • 文件格式驗證 :驗證是否符合規範 ,文本頭,主次版本驗證
      • 元數據驗證:保證描述符合java語言的要求
        • 類是否有父類
        • 是否繼承了不允許被繼承的類(final修飾過的類)
        • 如果這個類不是抽象類,是否實現其父類或接口中所有要求實現的方法
        • 類中的字段、方法是否與父類產生矛盾(如:覆蓋父類final類型的字段,或者不符合個則的方法)
      • 字節碼驗證:確定程序語義是合法的
      • 符號引用驗證:
        • 符號引用中通過字符串描述的全限定名是否能找到對應的類。
        • 在指定類中是否存在符合方法的字段描述符以及簡單名稱所描述的方法和字段。
        • 符號引用中的類、字段、方法的訪問性(private、protected、public、default)是否可被當前類訪問。
    • 準備
      • 是正式爲類變量分配內存並設置初始值階段。
        • public static int value = 123 初始時 value = 0;(因爲這時候尚未開始執行任何Java方法,而把value賦值爲123的putstatic指令是程序被編譯後,存放於類構造器<clinit>()方法之中,所以吧value賦值爲123的動作將在初始化階段纔會執行。)
        • static final int value = 123 初始時就是 value =123(常量)
      • !注意!這裏是類中的靜態變量設置初始值(操作的是JVM的方法區),不包含實例變量(實例變量存放在堆內存與對象實例在一起),實例變量是在對象實例化的時候初始化分配值得。
    • 解析
      • 是虛擬機將常量池中的符號引用替換爲直接引用的過程
        • 符號引用:一個java類將會編譯成一個class文件。在編譯時,java類並不知道所引用的類的實際地址,因此只能使用符號引用來代替,符號引用就是一組沒有歧義的字符串。
        • 直接引用:指針或者是地址偏移量。直接引用的對象一定是被加載到內存中。
    • 初始化
      • 執行類的構造器<clinit>:真正的開始執行類中定義的java程序代碼
      • 初始化靜態變量,靜態塊中的數據(一個類加載器只會初始化一次)
      • 在子類的<clinit>調用前要保證父類的<clinit>被調用
      • <clinit> 是線程安全的,執行<clinit>的線程需要先獲取鎖才能進行初始化操作,保證只有一個線程執行。(線程安全的懶漢單例模式)
        • public class SingTest {
          • private SingTest() {
          • }
          • private static class SingTestDemo{
          • private static SingTest getInstance = new SingTest(); }
          • public static SingTest getOurInstance(){
          • return SingTestDemo.getInstance; }
          • }
      • 對應的字節碼命令:new getstatic putstatic或invokestatic時
        • 對應的場景:
          • 使用new關鍵字實例對象
          • 讀取或者設置一個類的靜態字段(final修飾的除外)
          • 調用一個類的靜態方法
    • 使用
    • 卸載
  • ClassLoader 什麼是類加載器
    • ClassLoader 是一個抽象類
    • classLoader 可以定製,可以進行並行加載,使用的是委派機制
    • 可以定製
  • JVM中的類加載
    • 啓動類加載器 BootStrap ClassLoader 引導類裝入器是用本地代碼實現的類裝入器,它負責將 jdk中jre/lib下面的核心類庫或-Xbootclasspath選項指定的jar包加載到內存中。由於引導類加載器涉及到虛擬機本地實現細節,開發者無法直接獲取到啓動類加載器的引用,所以不允許直接通過引用進行操作。
    • 擴展類加載器 Extension ClassLoader 擴展類加載器是由Sun的ExtClassLoader(sun.misc.Launcher$ExtClassLoader)實現的。它負責將jdk中jre/lib/ext或者由系統變量-Djava.ext.dir指定位置中的類庫加載到內存中。開發者可以直接使用標準擴展類加載器。
    • 系統類加載器 System ClassLoader :系統類加載器是由 Sun的 AppClassLoader(sun.misc.Launcher$AppClassLoader)實現的。它負責將系統類路徑java -classpath或-Djava.class.path變量所指的目錄下的類庫加載到內存中。開發者可以直接使用系統類加載器。
  • 雙親委派機制
    • 每個加載器都有父類加載器,如果一個類加載器在接到類加載的請求時,它不會自己加載,而且把這個請求任務委託給父類加載器去完成,依次遞歸,如果父類加載器能完成任務,就返回成功,如果父類加載器不能完成任務,纔會自己加載。
    • 類的加載器中父子關係不是繼承的方式實現的,是以組合的關係方式複用父加載器的代碼的。
    • 優勢:使java類有一定的優先級的層次關係。相同類型的類在各種類加載環境中都是同一個類。如果不是雙親委派模型,那麼一樣的類的類型不相同。
      • Java類隨着加載它的類加載器一起具備了一種帶有優先級的層次關係。比如,Java中的Object類,它存放在rt.jar之中,無論哪一個類加載器要加載這個類,最終都是委派給處於模型最頂端的啓動類加載器進行加載,因此Object在各種類加載環境中都是同一個類。如果不採用雙親委派模型,那麼由各個類加載器自己取加載的話,那麼系統中會存在多種不同的Object類。
    • 案例一 雙親委派模型的問題:頂層ClassLoader,無法加載底層ClassLoader的類。 JDK的javax.xml.parsers包中定義了xml解析的類接口 Service Provider Interface SPI 位於rt.jar 即接口在啓動ClassLoader中。而SPI的實現類,可能由第三方提供,AppClassLoader進行加載。 解決思路:可以在線程中放入底層的ClassLoader到Thread. setContextClassLoader()中,然後在頂層ClassLoader中使用Thread.getContextClassLoader()獲得底層的ClassLoader進行加載第三方實現。
    • 案例二 Tomcat中使用了自定ClassLoader,並且也破壞了雙親委託機制。 每個應用使用WebAppClassloader進行單獨加載,他首先使用WebAppClassloader進行類加載,如果加載不了再委託父加載器去加載,這樣可以保證每個應用中的類不衝突。每個tomcat中可以部署多個項目,每個項目中存在很多相同的class文件(很多相同的jar包),他們加載到jvm中可以做到互不干擾。
    • 案例三: 利用破壞雙親委派來java的類熱部署實現(每次修改類文件,不需要重啓服務)。 因爲一個Class只能被一個ClassLoader加載一次,否則會報java.lang.LinkageError。當我們想要實現代碼熱部署時,可以每次都new一個自定義的ClassLoader來加載新的Class文件。JSP的實現動態修改就是使用此特性實現。  
      • 親身驗證:IDEA 開啓熱部署時,判斷類不一致,就是熱部署破壞了雙親委派模型,導致同一個類在不通的類加載器中。
    • ClassLoader加載.class文件的方式不僅限於從jar包中讀取,還可以從種地方讀取,因爲ClassLoader加載時需要的是byte[]數組.  ClassLoader加載Class文件方式:  
      • 從本地系統中直接加載
      • 通過網絡下載.class文件
      • 從zip,jar等歸檔文件中加載.class文件
      • 從專有數據庫中提取.class文件
      • 將Java源文件動態編譯爲.class文件 
  • jvm 調優
    • 沒有必要調整JVM,如果要調整一定是你的代碼有問題
    • CMS 收集器
    • G1收集器
    • 併發垃圾回收器
    • •不要把MaxGCPauseMillis設置的很小,優先設定一個GCTimeRatio來達到吞吐量的目標
    • CMS Full GC頻繁
    • •增大堆的容量
    • •調整CMSInitiatingOccupancyFraction,儘早啓動併發後臺線程
    • •增加後臺線程數
    • G1 Full GC頻繁
    • •增大堆容量
    • •調整InitiatingHeapOccupancyFraction,儘早啓動併發後臺線程
    • •增加後臺線程數
    • •Mixed GC
發佈了12 篇原創文章 · 獲贊 0 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章