天天看點

JVM類加載詳解

類的生命周期

JVM類加載詳解

類加載的時機

  • 遇到new、getstatic、putstatic或invokestatic這4條位元組碼指令時,
    • 如果類沒有初始化,則需要先觸發其初始化;
    • 這4條指令對應的的常見場景分别是:使用new關鍵字執行個體化對象、讀取或設定一個類的靜态字段 (被final修飾、已在編譯期把結果放入常量池的靜态字段除外) 的時候,以及調用一個類的靜态方法的時候。
    • 注:靜态内容是跟類關聯的而不是類的對象。
  • 使用java.lang.reflect包的方法對類進行反射調用的時候,如果類沒有進行過初始化,則需要先觸發其初始化。
    • 注:反射機制是在運作狀态中,對于任意一個類,都能夠知道這個類的所有屬性和方法;對于任意一個對象,都能夠調用它的任意一個方法和屬性;
    • 這種動态擷取的資訊以及動态調用對象的方法的功能稱為java 語言的反射機制
  • 當初始化一個類的時候,如果發現其父類還沒有進行過初始化,則需要先觸發其父類的初始化。
    • 注:子類執行構造函數前需先執行父類構造函數。
  • 當虛拟機啟動時,使用者需要指定一個要執行的主類(包含main()方法的那個類),虛拟機會先初始化這個主類。
    • main 方法是程式的執行入口
  • 當使用JDK1.7的動态語言支援時,如果一個java.lang.invoke.MethodHandle執行個體最後的解析結果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且這個方法句柄所對應的類沒有進行過初始化。則需要先觸發其初始化。
    • 注:JDK1.7 的一種新增的反射機制,都是對類的一種動态操作。

被動引用不會觸發類加載

  • 示範一:通過子類引用父類的靜态字段,不會導緻子類初始化
JVM類加載詳解
  • 示範二:通過數組定義來引用類,不會觸發此類的初始化
JVM類加載詳解
  • 示範三:常量在編譯階段會存入調用類的常量池中,本質上并沒有直接引用到定義常量的類,是以不會觸發定義常量的類的初始化
JVM類加載詳解

類加載過程

  • 加載
    • 通過一個類的全限定名來擷取此類的二進制位元組流;
      • 注:虛拟機規範并沒有明确說明類的二進制位元組流從何而來,是以這裡可以有非常靈活的實作空間,例如可以用過ZIP 包(如JAR 、EAR 、WAR 格式)讀取,從網絡中擷取,運作時計算生成(如ASM 架構),從資料庫中讀取等等。
      • 将這個位元組流所代表的靜态存儲結構轉化為方法區的運作時資料結構;
      • 方法區域Java 堆一樣,是各線程共享的記憶體區域,它用于存儲已被虛拟機加載的類資訊、常量、靜态變量、即時編譯後的代碼等資料”。而方法區中的資料存儲結構格式虛拟機自行定義。
    • 在記憶體中生成一個代表這個類的java.lang.Class對象,作為方法區這個類的各種資料的通路入口
      • 注:加載階段完成後,虛拟機在記憶體中執行個體化一個java.lang.Class 類的對象(Class是一個實實在在的對象,是記錄着類成員、接口等資訊的對象)。還有一點是,我們都知道對象肯定是存放在堆中的,但Class 對象比較特殊,對于HotSpot 虛拟機而言,Class 對象是存放在方法區中的。
  • 驗證
    • 驗證位元組流是否符合Class檔案格式的規範,并且能被目前版本的虛拟機處理
      • 是否以魔數0xCAFEBABE開頭;
      • 主、次版本号是否在目前虛拟機處理範圍内;
      • 常量池的常量中是否有不被支援的常量類型(檢查常量tag标志);
      • 指向常量的各種索引值中是否有指向不存在的常量或不符合類型的常量
      • CONSTANT_Utf8_info型的常量中是否有不符合UTF8編碼的資料
      • 。。。。
    • 中繼資料驗證,保證不存在不符合Java 語言規範的中繼資料資訊
      • 這個類是否有父類(除了java.lang.Object之外,所有的類都應當有父類);
      • 這個類的父類是否繼承了不允許被繼承的類(被final修飾的類);
      • 如果這個類不是抽象類,是否實作了其父類或接口之中要求實作的所有方法;
      • …..
    • 位元組碼驗證,保證被校驗類的方法在運作時不會做出危害虛拟機安全的事件
      • 保證任意時刻操作棧的資料類型與指令代碼序列都能配合工作,例如不會出現類似這樣的情況:在操作棧放置了一個int類型的資料,使用時卻按long類型來加載如本地變量中;
      • 保證跳轉指令不會跳轉到方法體以外的位元組碼指令上;
      • 保證方法體中的類型轉換是有效的;
      • ……
    • 符号引用驗證,確定在後續的“解析”階段能正常執行
      • 符号引用中通過字元串描述的全限定名是否能找到對應的類;
      • 在指定類中是否存在符合方法的字段描述符以及簡單名稱所描述的方法和字段;
      • …..
  • 準備
    • 準備階段是正式為類變量配置設定記憶體設定類變量初始化值的階段,這些變量所使用的記憶體都将在方法區中進行配置設定。
      • 這時候進行記憶體配置設定的僅包括類變量(被static修飾的變量),而不包括執行個體變量
      • 這裡所說的初始值“通常情況”下是資料類型的零值,假設一個類變量的定義為:public static int value =123那變量value在準備階段過後的初始化值為0而不是123,
      • 因為這是尚未開始執行任何Java方法,而把value指派為123的putstatic指令是程式被編譯後存放在類構造器()方法之中,是以把value指派為123的動作将在初始化階段才會執行。
  • 類或接口的解析

類加載器(雙親委派機制)

JVM類加載詳解
  • 啟動類加載器(BootstrapClassLoader):這個類加載器負責将\lib目錄中的,或被-Xbootclasspath參數所指定的路徑中的,并且是虛拟機識别的(如rt.jar)類庫加載到虛拟機記憶體中。
    • 啟動類加載器(BootstrapClassLoader):這個類加載器負責将\lib目錄中的,或被-Xbootclasspath參數所指定的路徑中的,并且是虛拟機識别的(如rt.jar)類庫加載到虛拟機記憶體中。
    • JVM類加載詳解
  • 擴充類加載器(ExtensionClassLoader):這個加載器由sun.misc.Launcher$ExtClassLoader實作的,它負責加載\lib\ext目錄中的,或者被java.ext.dirs系統變量所指定的路徑中的所有類庫,開發者可以直接使用擴充類加載器,如下示例:
    • 是我的ClassLoaderTest類在classpath下列印的結果:
    • JVM類加載詳解
    • 是我的ClassLoaderTest類打包放在/lib/ext目錄下的列印結果

    -
    JVM類加載詳解

  • 應用程式類加載器(ApplicationClassLoader):從上面的測試可以看到,這個類加載器由sun.misc.Launcher$AppClassLoader實作,由于這個類加載器是ClassLoader中的getSystemClassLoader()方法的傳回值,是以一般也稱為系統類加載器。它負責加載使用者類路徑(ClassPath)上所指定的類庫,開發者可以直接使用這個類加載器,如果應用程式中沒有自定義自己的類加載器,一般情況下這個就是程式中預設的類加載器。
  • 自定義類加載器(UserClassLoader):所有自定義的類加載器必須繼承ClassLoader抽象類(嚴格說所有類加載器都繼承于它,除了Bootstrap ClassLoader,因它是由C/C++實作的)
    • getParent() 傳回該類加載器的父類加載器。
    • loadClass(String name)加載名稱為 name的類,傳回的結果是 java.lang.Class類的執行個體。
    • findClass(String name)查找名稱為 name的類,傳回的結果是 java.lang.Class類的執行個體。
    • findLoadedClass(String name)查找名稱為 name的已經被加載過的類,傳回的結果是 java.lang.Class類的執行個體。
    • defineClass(String name, byte[] b, int off, int len)把位元組數組 b中的内容轉換成 Java 類,傳回的結果是 java.lang.Class類的執行個體。這個方法被聲明為 final的。
    • resolveClass(Class
public class MyClassLoader extends ClassLoader{      
      public MyClassLoader(){}  

      public MyClassLoader(ClassLoader parent){  
         super(parent);  
      }     
      @Override10    
      protected Class<?> findClass(String name) throws ClassNotFoundException{ 
               byte[] bytes = null;        
               /*擷取類位元組,自定義類我預設在G盤*/        
               FileInputStream fis = null;       
               try {    
                    fis = new FileInputStream("G:\\"+name+".class");           ByteArrayOutputStream baos = new ByteArrayOutputStream();        byte[] buff = new byte[];            
                    int rc = ; 
             while ((rc = fis.read(buff, , )) > ) { 
                 baos.write(buff, , rc); 
                 } 
                bytes = baos.toByteArray();                  

               } catch (Exception e) {             

                  e.printStackTrace();        

               }finally{             
                    if(fis != null){                
                    try {                   
                        fis.close();              

                    } catch (IOException e) {                     
                        e.printStackTrace();               
                    }       
                } 
            }
            /*生産Class對象*/     
            try{
                Class<?> c = this.defineClass(name, bytes, , bytes.length);  
             return c;  
         }catch (Exception e){  
             e.printStackTrace();  
        }  
            return super.findClass(name);
        }   
        public static void main(String[] args) throws Exception{       
            MyClassLoader mlc = new MyClassLoader(MyClassLoader.getSystemClassLoader());        
            Class c = mlc.loadClass("MyClassLoaderTest");         System.out.println(c.newInstance().toString());   
        }  
   }