1. 程式人生 > >java中類加載時機

java中類加載時機

sets thread jdk AI 生命 不可訪問 需要 應該 int()

類加載 在class文件中的描述信息都需要加載到jvm才能運行和使用。 jvm的類加載機制:jvm把描述類的數據從class文件中加載到內存,並對數據進行校驗,轉換解析,和初始化,最終形成被jvm使用的Java類型。 生命周期:加載-》驗證-》準備-》解析-》初始化-》使用-》卸載 加載到初始化都是在程序的與運行期間完成的。驗證,準備,解析也叫連接過程,Java的特性是依賴在運行期動態加載和動態連接。 會初始化的情況:(主動引用) 1.遇到new(用new實例對象),getStatic(讀取一個靜態字段),putstatic(設置一個靜態字段),invokeStatic(調用一個類的靜態方法) 2.使用Java.lang.reflect包的方法對類進行反射調用時,如何此時類沒有進行init,會先init。 3.當初始化一個類時,如果其父類沒有進行初始化,先初始化父類 4.jvm啟動時,用戶需要指定一個執行的主類(包含main的類)虛擬機會先執行這個類 5.當java.lang.invoke.MethodHandler實例後的結果是REF-Static/putstatic/invokeStatic的句柄,這個句柄對應的額類沒初始化的話應該首先初始。 註意:除以上5中方法外,所有引用類的方法都不會觸發初始化,稱為被動引用。 被動引用的例子: 1.通過子類來引用父類的靜態字段,只會觸發父類的初始化,不會觸發子類的初始化。 2.superclass () sc = new superclass[];//不會觸發superclass初始化,因為底層實現是直接生成object子類。 3.引用一個類的靜態常量也不會觸發初始化,因為常量在編譯階段已經確認。 =====》接口也會有初始化的過程,但接口中不能有static塊,但編譯器也會為接口生成<clinit>類構造器,用於初始化接口中成員變量,接口子類的不同僅是上邊3的不同,因為接口不要求父接口全部實現,而是用到哪些實現哪些。 類加載的五個過程 1.加載: 1.通過類的全限定名來獲取類的二進制字節流(用戶可操作,自定義類加載器(實現通過一個類的全限定名獲取類的二進制字節流的動作放在jvm外部實現的模塊)) 2.將這個字節流所代表的靜態存儲結構轉化為在方法區的運行時數據結構。 3.在內存中生成代表這個類的Java.lang.class類對象,作為這個數據訪問的入口。 註:數組類本身不是通過類加載器創建,而是有jvm直接創建 2.驗證: 防止危害jvm安全,目的是確保class文件中字節流中包含的信息符合當前虛擬機的要求。 主要有四個方面的驗證:1.文件格式驗證,是否以魔數開始,版本信息是否為jvm接受,常量池中是否有不支持的類型。 2.元數據驗證:進行語法分析,是否每個類都有父類,是否有語法錯誤,是否繼承自final。 3.字節碼驗證:語義分析,通過數據流和控制流分析,確保語義是合法的。 4.符號引用驗證:是否能找到對應的類。發生在講符號引用轉化為直接引用時,在解析中產生。 3.準備:正式為類變量分配內存,在方法區中分配 註意:static+ final修飾的變量在準備階段之後就是用戶指定的值。 4.解析:將符號引用轉化為直接引用(可選擇)包括類,接口, 字段,方法的解析。 5.初始化:真正執行Java程序中的代碼(字節碼),是執行類構造器的過程, 對類的靜態變量和代碼塊執行初始化工作。 1.<clinit>()方法是由於編譯器自動收集類中所有類變量賦值,靜態語句塊合並產生的,順序是語句在源文件中的順序。 2.《clinit》方法與類的構造器不同,他不需要顯示的調用父類的構造方法,因為vm會保證在子類的clinit方法執行之前, 父類已經執行了。所以jvm執行的clinit一定是object類。 3.如果一個類或者接口中沒有靜態語句或者靜態塊,則可以沒有clinit方法。 4.jvm會保證類的clinit方法加鎖。 註意:靜態語句塊中只能訪問定義在靜態語句之前的變量,不能訪問語句之後的,但可以為後邊的變量賦值。 eg: class Test{ static{ i = 4; //正常 System.out.println(i)//報錯,因為不能訪問,只能賦值 } static int i = 2; } 底層實現: 類裝載工作是有ClassLoader及其子類負責的,ClassLoader是一個運行時組件,它負責在運行時查找和裝入Class字節碼文件。 jvm有三個ClassLoader:依次是: 根裝載器,用戶不可訪問。 ExtClassLoader(擴展類裝載器,裝載jar包), APPClassLoader(系統類裝載器:classpath下的其他類) jvm使用“”全盤負責委托機制”來裝載:是指裝載是一定是下通過其父類去找目標類,找不到才去找子類,這是出於安全的考慮,如果反過來,我們每個人都可以隨意修改API了,不安全。 String 類就是這樣的。 ClassLoader 是Java.lang下的一個抽象方法, 1.Class loadClass(String name);name必須是參數的全限定名, 2.Class defineClass(String name,bytr[],int off,int len); 3.Class findLoadedClass(String name),查看ClassLoader是否已經裝入某個類,裝了返回class。,沒有裝返回null. 註意:每一個類在jvm中都擁有一個Java.lang.class對象,是對象在裝載時jvm通過調用類加載器中的defineClass()方法自動構造的。 Java反射機制: Class反射對象描述的是類的語義結構,通過class對象,可以獲取構造器,成員變量,方法,,等類元素的反射對象,並且可以用編程的方法通過這些反射對象對目標對象進行操作。 這些反射類在java.lang.reflect包中定義,下面是最主要的三個類: 1.Constructor:類的構造函數反射類:通過Class#getConstructors()方法可以獲得類的所有構造函數的反射對象數組。其中最主要的方法是newInstance(Object[] args),通過該方法可以創建一個對象類的實例,功能和new一樣。在jdk5.0之後,提供了newInstance(Object...args)更為靈活。 2.Method:類方法的反射類。通過Class#getDeclaredMethods()方法可以獲取所有方法的反射類對象數組Method[].其中最主要的方法是 invoke(String name,class parameterTypes),和invoke(Object obj,Object...args)。同時也還有很多其他方法: A:Class getReturnType():獲取方法的返回值類型 B:Class[] getParameterTypes():獲取方法的參數數組 3.Field:類成員變量的反射類,通過Class#getDeclareFields()可以獲取類成員變量反射的數組。 Class#getDeclareField(String name)獲取某特定名稱的反射對象。 最主要的方法是:set(Object obj,Object value),為目標對象的成員變量賦值。如果是基礎類型還可以這樣賦值 setInt(),setString()................ java還提供了包的反射類和註解的反射類。。 總結:java反射體系保證了通過程序化的方式訪問目標對象的所有元素,對於private 和protected成員變量或者方法,也是可以訪問的。 主要就是取消訪問檢查 Field c = cls.getDeclaredField("a"); c.setAccessible(true);//這裏如果不設置的話,會拋出IllegalAccessException()異常 c.set(car, 888); ... 下面是代碼:
package
com.ioc; public class Car { private String brant; private String color; private int maxSpeed; private int a; public String getBrant() { return brant; } public void setBrant(String brant) {
this.brant = brant; } public String getColor() { return color; } public void setColor(String color) { this.color = color; } public int getMaxSpeed() { return maxSpeed; }
public void setMaxSpeed(int maxSpeed) { this.maxSpeed = maxSpeed; } public Car(){} public Car(String brant,String color,int max) { this.brant = brant; this.color = color; this.maxSpeed = max; } public String toString(){ String a = brant+color+maxSpeed; return a; } public void print(){ System.out.println(brant+color+maxSpeed+a); } }

反射調用的代碼:
package com.ioc;  
  
import java.lang.reflect.Constructor;  
import java.lang.reflect.Field;  
import java.lang.reflect.Method;  
import com.ioc.Car;  
public class ReflectTest {  
    public static Car initByDefaultConst()throws Throwable{  
        ClassLoader loader = Thread.currentThread().getContextClassLoader();  
        Class cls = loader.loadClass("com.ioc.Car");  
          
        Constructor cons = cls.getDeclaredConstructor((Class[])null);  
        Car car = (Car) cons.newInstance();  
          
        Field c = cls.getDeclaredField("a");  
        c.setAccessible(true);  
        c.set(car, 888);  
        Method setBrand = cls.getMethod("setBrant",String.class);  
        setBrand.invoke(car, "aaaaa");  
        Method setColor = cls.getMethod("setColor",String.class);  
        setBrand.invoke(car, "red");  
        Method setMaxSpeed = cls.getMethod("setMaxSpeed",int.class);  
        setBrand.invoke(car, "11");  
          
        return car;  
    }  
    public static void main(String[] args) throws Throwable{  
        Car car = initByDefaultConst();  
        car.print();  
        System.out.println(car.toString());  
    }  
}  



QA:在編程中我們常會遇到java.lang.NoSuchMethodError,的錯誤信息,為什麽? --------->>>>>其實就是由於全盤負責委托機制引發的,比如:既引入 了commons.lang3.x.jar又引入了,commons.lang.4.x.jar那就會拋出這個異常。

驗證一下:

驗證:

1)當類被初始化時,其靜態代碼塊會執行。

class ClassLoadTime{

  static{

    System.out.println("ClassLoadTime類初始化時就會被執行!");

  }

  public ClassLoadTime(){

    System.out.println("ClassLoadTime構造函數!");

  }

}

class ClassLoadDemo{

  public static void main(String[] args){

    ClassLoadTime  clt = new ClassLoadTime();

  }

}

輸出結果:

ClassLoadTime類初始化時就會被執行!

ClassLoadTime構造函數!


2) 讀取一個類的靜態字段(被final修飾、已在編譯期把結果放在常量池的靜態字段除外)

class ClassLoadTime{

  static{

    System.out.println("ClassLoadTime類初始化時就會被執行!");

  }

  public static int max = 200; (防止測試類和此類不在一個包,使用public修飾符)

  public ClassLoadTime(){

    System.out.println("ClassLoadTime構造函數!");

  }

}

class ClassLoadDemo{

  public static void main(String[] args){

    int  value = ClassLoadTime.max;

    System.out.println(value);

  }

}

輸出:

ClassLoadTime類初始化時就會被執行!

200


3)設置一個類的靜態字段(被final修飾、已在編譯期把結果放在常量池的靜態字段除外)

class ClassLoadTime{

  static{

    System.out.println("ClassLoadTime類初始化時就會被執行!");

  }

  public static int max = 200; (防止測試類和此類不在一個包,使用public修飾符)

  public ClassLoadTime(){

    System.out.println("ClassLoadTime構造函數!");

  }

}

class ClassLoadDemo{

  public static void main(String[] args){

      ClassLoadTime.max = 100;

  }

}

輸出:

ClassLoadTime類初始化時就會被執行!


4)調用一個類的靜態方法

class ClassLoadTime{

  static{

    System.out.println("ClassLoadTime類初始化時就會被執行!");

  }

  public static int max = 200; (防止測試類和此類不在一個包,使用public修飾符)

  public ClassLoadTime(){

    System.out.println("ClassLoadTime構造函數!");

  }

  public static void method(){

    System.out.println("靜態方法的調用!");

  }

}

class ClassLoadDemo{

  public static void main(String[] args){

      ClassLoadTime.method();

  }

}

輸出:

ClassLoadTime類初始化時就會被執行!

靜態方法的調用!


被final修飾靜態字段在操作使用時,不會使類進行初始化,因為在編譯期已經將此常量放在常量池。

測試:

class ClassLoadTime{

  static{

    System.out.println("ClassLoadTime類初始化時就會被執行!");

  }

  public static final int MIN = 10; (防止測試類和此類不在一個包,使用public修飾符)

}

class ClassLoadDemo{

  public static void main(String[] args){

   System.out.println(ClassLoadTime.MIN);

  }

}

輸出:

10


子類調用或者設置父類的靜態字段或者調用父類的靜態方法時僅僅初始化父類,而不初始化子類。同樣讀取final修飾的常量不會進行類的初始化。

class Fu{

  public static int value = 20;

  static{

    System.out.println("父類進行了類的初始化!");

  }

}

class Zi{

  static{

    System.out.println("子類進行了類的初始化!");

  }

}

class LoadDemo{

  public static void main(String[] args){

    System.out.println(Zi.value);    

  }

}

輸出:

父類進行了類的初始化!

20


java類中各種成員的初始化時機,此處不一一測試:

類變量(靜態變量)、實例變量(非靜態變量)、靜態代碼塊、非靜態代碼塊 的初始化時機:
* 由 static 關鍵字修飾的(如:類變量[靜態變量]、靜態代碼塊)將在類被初始化創建實例對象之前被初始化,而且是按順序從上到下依次被執行;

   public static int value =34;

   static{

  System.out.println("靜態代碼塊!");

 }

 public 類名(){    

  System.out.println("構造函數!");

 }

一旦這樣寫,在類被初始化創建實例對象之前會先初始化靜態字段value,然後執行靜態代碼塊,當實例化對象時會執行構造方法中的代碼
* 沒有 static 關鍵字修飾的(如:實例變量[非靜態變量]、非靜態代碼塊)初始化實際上是會被提取到類的構造器中被執行的,但是會比類構造器中的
代碼塊優先執行到,其也是按順序從上到下依次被執行。

 public int value =34;

   {

  System.out.println("非靜態代碼塊!");

 }

 public 類名(){    

  System.out.println("構造函數!");

 }

在使用構造函數實例化一個對象時,會先初始化value,然後執行非靜態代碼塊,最後執行構造方法裏面的代碼。

*在存在父類的時候,調用子類的構造時,會先調用父類的默認構造(空參構造),進行父類的初始化。

參考:java中類加載時機

參考:Java 類加載時機和順序

參考:Java類加載機制和反射機制

java中類加載時機