JVM-類加載機制(1)

類加載

class loading

  • 在java代碼中,類的加載、連接和初始化都是在程序運行期間完成的。(類從磁盤加載到內訓中經理的三個階段)
  • 提供了更大的靈活性,增加了更多的可能性。

類加載器深入剖析:

Java虛擬機與程序的生命週期

  • 在如下幾種情況下,java虛擬機將結束生命週期
    (1) 執行了System.exit()方法
    (2)程序正常執行結束
    (3)程序在執行過程中遇到了異常或錯誤而異常終止
    (4)由於操作系統出現錯誤而導致虛擬機進程終止

類的加載、連接與初始化

  • 加載:查找並加載類的二進制數據到java虛擬機中
  • 連接:
    驗證 : 確保被加載的類的正確性
    準備:爲類的靜態變量分配內存,並將其初始化爲默認值,但是到達初始化之前類變量都沒有初始化爲真正的初始值
    解析:把類中的符號引用轉換爲直接引用,就是在類型的常量池中尋找類、接口、字段和方法的符號引用,把這些符號引用替換成直接引用的過程
  • 初始化:爲類的靜態變量賦予正確的初始值

類從磁盤上加載到內存中要經歷五個階段:加載、連接、初始化、使用、卸載

Java程序對類的使用方式可分爲兩種
(1)主動使用
(2)被動使用

  • 所有的Java虛擬機實現必須在每個類或接口被Java程序“首次主動使用”時才能初始化他們
  • 主動使用(七種)
    (1)創建類的實例
    (2)訪問某個類或接口的靜態變量 getstatic(助記符),或者對該靜態變量賦值 putstatic
    (3)調用類的靜態方法 invokestatic
    (4)反射(Class.forName(“com.test.Test”))
    (5)初始化一個類的子類
    (6)Java虛擬機啓動時被標明啓動類的類
    (7)JDK1.7開始提供的動態語言支持
  • 被動使用
    除了上面七種情況外,其他使用java類的方式都被看做是對類的被動使用,都不會導致類的初始化

類的加載詳解:

類的加載指的是將類的.class文件中的二進制數據讀入到內存中,將其放在運行時數據區的方法區內,然後在內存中創建一個java.lang.Class對象(規範並未說明Class對象位於哪裏,HotSpot虛擬機將其放在方法區中)用來封裝內在方法區內的數據結構。

  • 加載.calss文件的方式
    (1)從本地系統中直接加載
    (2)通過網絡下載.class文件
    (3)從zip,jar等歸檔文件中加載.class文件
    (4)從專用數據庫中提取.class文件
    (5)將java源文件動態編譯爲.class文件

  • 測試1:

		/**
		對於靜態字段來說,只有直接定義了該字段的類纔會被初始化當一個類在初始化時,
		要求父類全部都已經初始化完畢
		-XX:+TraceClassLoading,用於追蹤類的加載信息並打印出來
		-XX:+<option>,表示開啓option選項 -XX:-<option>,表示關閉option選項  
		-XX:<option>=value,表示將option的值設置爲value
		**/
	
		public class Test1{  
           public static void main(String[] args){  
	           System.out.println(MyChild.str); //輸出:MyParent static block 、 hello world (因爲對MyChild不是主動使用)  
		       System.out.println(MyChild.str2); //輸出:MyParent static block 、MyChild static block、welcome  
         }  
       }  
       class MyParent{  
           public static String str="hello world";  
        static {  
               System.out.println("MyParent static block");  
         }  
       }  
       class MyChild extends MyParent{  
           public static String str2="welcome";  
        static {  
               System.out.println("MyChild static block");  
         }  
       }
  • 測試2:
/**
        常量在編譯階段會存入到調用這個常量的方法所在的類的常量池中
        本質上,調用類並沒有直接調用到定義常量的類,因此並不會觸發定義常量的類的初始化
        注意:這裏指的是將常量存到MyTest2的常量池中,之後MyTest2和MyParent就沒有任何關係了。
        甚至我們可以將MyParent2的class文件刪除

        助記符 ldc:表示將int、float或者String類型的常量值從常量池中推送至棧頂
        助記符 bipush:表示將單字節(-128-127)的常量值推送到棧頂
        助記符 sipush:表示將一個短整型值(-32768-32369)推送至棧頂
        助記符 iconst_1:表示將int型的1推送至棧頂(iconst_m1到iconst_5)
*/
public class MyTest2{
    public static void main(String[] args){
        System.out.println(MyParent2.str);    //輸出 hello world
        System.out.println(MyParent2.s);  
        System.out.println(MyParent2.i);  
        System.out.println(MyParent2.j);  
    }
}
class MyParent2{
    public static final String str="hello world";
    public static final short s=7;
    public static final int i=129;
    public static final int j=1;
    static {
        System.out.println("MyParent static block");
    }
}
  • 測試3
/**
        當一個常量的值並非編譯期間可以確定的,那麼其值就不會放到調用類的常量池中
        這時在程序運行時,會導致主動使用這個常量所在的類,顯然會導致這個類被初始化
*/
public class MyTest3{
    public static void main(String[] args){
        System.out.println(MyParent3.str);  //輸出MyParent static block、kjqhdun-baoje21w-jxqioj1-2jwejc9029
    }
}
class MyParent3{
    public static final String str=UUID.randomUUID().toString();
    static {
        System.out.println("MyParent static block");
    }
}
  • 測試4
/**
        對於數組實例來說,其類型是由JVM在運行期動態生成的,表示爲 [L com.hisense.classloader.MyParent4 這種形式。
        對於數組來說,JavaDoc經構成數據的元素成爲Component,實際上是將數組降低一個維度後的類型。
        
        助記符:anewarray:表示創建一個引用類型(如類、接口)的數組,並將其引用值壓入棧頂
        助記符:newarray:表示創建一個指定原始類型(int boolean float double)d的數組,並將其引用值壓入棧頂
*/
public class MyTest4{
    public static void main(String[] args){
        MyParent4 myParent4=new MyParent4();        //創建類的實例,屬於主動使用,會導致類的初始化
        MyParent4[] myParent4s=new MyParent4[1];    //不是主動使用
        System.out.println(myParent4s.getClass());          //輸出 [L com.hisense.classloader.MyParent4
        System.out.println(myParent4s.getClass().getSuperClass());    //輸出Object
        
        int[] i=new int[1];
        System.out.println(i.getClass());          //輸出 [ I
        System.out.println(i.getClass().getSuperClass());    //輸出Object
    }
}
class MyParent4{
    static {
        System.out.println("MyParent static block");
    }
}
  • 測試5
/**
        當一個接口在初始化時,並不要求其父接口都完成了初始化
        只有在真正使用到父接口的時候(如引用接口中定義的常量),纔會初始化
*/
public class MyTest5{  
        public static void main(String[] args){  
            System.out.println(MyChild5.b);  
  }  
    }  
interface MParent5{  
    public static Thread thread= new Thread() {  
        {  
            System.out.println(" MParent5 invoke");  
  }  
    };  
}  
interface MyChild5 extends MParent5{     //接口屬性默認是 public static final  public static int b=6;  
}

-測試6

/**
        準備階段和初始化的順序問題
*/
public class MyTest6{
    public static void main(String[] args){
         public static void main(String[] args){
            Singleton Singleton=Singleton.getInstance();
            System.out.println(Singleton.counter1);     //輸出1,1
            System.out.println(Singleton.counter2);
         }
    }
}
class Singleton{
    public static int counter1;
    public static int counter2=0;               /
    private static Singleton singleton=new Singleton();
    
    private Singleton(){
        counter1++;
        counter2++;
    }
    
    // public static int counter2=0;       //   若改變此賦值語句的位置,輸出  1,0
    public static Singleton getInstance(){
        return singleton;
    }
}
  • 初始化
    在初始化階段,Java虛擬機執行類的初始化語句,爲類的靜態變量賦予初始值。在程序中,靜態變量的初始化有兩種途徑:
    (1)在靜態變量的聲明處進行初始化;
    (2)在靜態代碼塊中進行初始化。
    類的初始化步驟:
    (1)假如這個類還沒有被加載和連接,那就先進行加載和連接
    (2)假如類存在直接父類,並且這個父類還沒有被初始化,那就先初始化直接父類
    (3)假如類中存在初始化語句,那就依次執行這些初始化語句
    ·當java虛擬機初始化一個類時,要求它的所有父類都已經被初始化,但是這條規則不適用於接口。因此,一個父接口並不會因爲它的子接口或者實現類的初始化而初始化。只有當程序首次使用特定的接口的靜態變量時,纔會導致該接口的初始化。
    ·調用ClassLoader類的loadClass方法加載一個類,並不是對類的主動使用,不會導致類的初始化。

類加載器的父親委託機制

在父親委託機制中,各個加載器按照父子關係形成了樹形結構,除了根加載器之外,其餘的類加載器都有一個父加載器

  • 若有一個類能夠成功加載Test類,那麼這個類加載器被稱爲定義類加載器,所有能成功返回Class對象引用的類加載器(包括定義類加載器)稱爲初始類加載器。

    -測試7
/**
        java.lang.String是由根加載器加載,在rt.jar包下
*/
public class MyTest7{
    public static void main(String[] args){
         public static void main(String[] args){
            Class<?> clazz=Class.forName("java.lang.String");
            System.out.println(clazz.getClassLoader());  //返回null
            
            Class<?> clazz2=Class.forName("C");
           System.out.println(clazz2.getClassLoader());  //輸出sun.misc.Launcher$AppClassLoader@18b4aac2  其中AppClassLoader:系統應用類加載器
         }
    }
}
class C{
}
  • 測試8
/**
        調用ClassLoader的loaderClass方法加載一個類,並不是對類的主動使用,不會導致類的初始化
*/
public class MyTest8{
    public static void main(String[] args){
         public static void main(String[] args){
            ClassLoader loader=ClassLoader.getSystemClassLoader();
            Class<?> clazz1=loader.loadClass("CL"); //不會初始化
            System.out.println(clazz1);
            System.out.println("-------------------");
            
            Class<?> clazz=Class.forName("CL");
            System.out.println(clazz);  //反射初始化
         }
    }
}
class CL{
    static {
        System.out.println("FinalTest static block);
    }
}
  • 測試9
public class Test9 {  
    static {  
        System.out.println("MyTest9 static block");  
  }  
  
    public static void main(String[] args) {  
        System.out.println(Child.b);  
  }  
}  
class Parent{  
    static int a =3;  
 static {  
        System.out.println("Parent static block");  
  }  
}  
class Child extends Parent{  
    static int b =4;  
 static {  
        System.out.println("Child static block");  
  }  
}
//輸出結果:
MyTest9 static block
Parent static block
Child static block
4
  • 測試10
class Parent2{  
    static int a = 3;  
 static {  
        System.out.println("Parent2 static block");  
  }  
}  
class Child2 extends Parent2{  
    static int b = 4;  
 static {  
        System.out.println("Child2 static block");  
  }  
}  
public class Test10 {  
    static {  
        System.out.println("MyTest10 static block");  
  }  
  
    public static void main(String[] args) {  
        Parent2 parent2;  
  System.out.println("-------");  
  parent2 = new Parent2();  
  System.out.println("-------");  
  System.out.println(parent2.a);  
  System.out.println("-------");  
  System.out.println(Child2.b);  
  }  
}
//輸出結果:
MyTest10 static block
-------
Parent2 static block
-------
3
-------
Child2 static block
4
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章