1. 程式人生 > >JVM總括四-類載入過程、雙親委派模型、物件例項化過程 JVM思考-init和clinit區別

JVM總括四-類載入過程、雙親委派模型、物件例項化過程 JVM思考-init和clinit區別

JVM總括四-類載入過程、雙親委派模型、物件例項化過程


目錄:JVM總括:目錄

一、 類載入過程

類載入過程就是將.class檔案轉化為Class物件,類例項化的過程,(User user = new User(); 這個過程是物件例項化的過程);

一個.class檔案只有一個Class物件(位元組碼物件),可以有無數個物件(例如:new User(););

 

1、Load:

  將編譯後的.class檔案以二進位制流的方式載入到JVM記憶體中,並轉化為特定的資料結構,用到的就是classLoad二類載入器。這個過程中校驗cafe babe魔法數常量池檔案長度

是否有父類等。

2、Link:

  分為驗證、準備、解析三步。

  驗證:更為詳細的驗證,比如:final是否規範(二次賦值不規範)、static是否合理(靜態方法必須引用靜態變數)、型別是否正確

  準備:靜態變數分配記憶體並設定預設值(靜態變數不賦值是有個預設值得),這個過程沒有物件例項化。

  解析:把類中的符號引用轉化為直接引用,完成記憶體結構佈局。(符號引用轉化為直接引用:例如:test1() { test2(); },這裡test1呼叫test2方法就是符號引用,但實際test2()通過一個指標指向test2方法的記憶體地址,這個指標負責呼叫,它就是直接引用)。

3、Init:

  執行類構造器<clinit>,執行靜態程式碼塊為靜態變數賦予正確的初始值遞迴初始化父類執行建構函式。(先執行靜態程式碼塊,再執行靜態變數)。

任何小寫的class定義的類都有一個魔法屬性:class,

int j;
Class<Integer> integerClass = int.class;
Class<Integer> integerClass1 = Integer.class;

二、類載入的原則:雙親委派模型

  類載入是怎麼確定類檔案的位置呢?

  1、Bootstrap:最高階的類載入器,裝置最核心的類,如:Object、System、String;

  2、Extension ClassLoader:JDK9之前的類載入器,以後的為Platform ClassLoader,載入系統的擴充套件類;

  3、Application ClassLoader:應用類載入器,主要載入使用者自定義CLASSPATH路徑下的類。

類載入器並非繼承關係,只是以組合的方式服用父載入器功能,符合優先原則。

其中第二、第三層為java實現,使用者可以自定義類載入器,第三層為C++實現,所有為null:

ClassLoader classLoader = Son.class.getClassLoader();
ClassLoader parent = classLoader.getParent();
ClassLoader parent1 = parent.getParent();
System.out.println(classLoader);
System.out.println(parent);
System.out.println(parent1);
----------------------------------- [email protected] [email protected]
null

 

獲取Bootstrap載入的類庫:

URLClassPath bootstrapClassPath = Launcher.getBootstrapClassPath();
URL[] urLs = bootstrapClassPath.getURLs();
for (URL url : urLs){
     System.out.println(url.toExternalForm());
}
--------------------------------------------------------- file:
/D:/jdk-8u151-windows-x64/JDK/jre/lib/resources.jar file:/D:/jdk-8u151-windows-x64/JDK/jre/lib/rt.jar file:/D:/jdk-8u151-windows-x64/JDK/jre/lib/sunrsasign.jar file:/D:/jdk-8u151-windows-x64/JDK/jre/lib/jsse.jar file:/D:/jdk-8u151-windows-x64/JDK/jre/lib/jce.jar file:/D:/jdk-8u151-windows-x64/JDK/jre/lib/charsets.jar file:/D:/jdk-8u151-windows-x64/JDK/jre/lib/jfr.jar file:/D:/jdk-8u151-windows-x64/JDK/jre/classes

Bootstrap載入的路徑可以追加,在JVM增加啟動引數,不過不建議修改:

Xbootclasspath/D:/test/src

 自定義類載入器的情況:

1、隔離載入類:框架中吧類載入到不同的環境中

2、修改類載入方式:除了Bootstrap,其他類並非一定要加入。

3、擴充套件載入源:比如載入資料庫;

4、防止原始碼洩露:可以對類進行加密,那載入的時候需要自定義載入器對類進行解密載入。

自定義ClassLoder,繼承ClassLoader,重寫findClass(),呼叫defineClass():

public class UserClassLoader extends ClassLoader {

    private String classpath;

    public UserClassLoader(String classpath) {
        this.classpath = classpath;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            byte [] classDate=getDate(name);
            if(classDate==null){
                throw new FileNotFoundException();
            }
            else{
                //defineClass方法將位元組碼轉化為類
                return defineClass(name,classDate,0,classDate.length);
            }

        } catch (IOException e) {
            e.printStackTrace();
        }

        return super.findClass(name);
    }
    //返回類的位元組碼
    private byte[] getDate(String className) throws IOException{
        InputStream in = null;
        ByteArrayOutputStream out = null;
        String path=classpath + File.separatorChar +className.replace('.',File.separatorChar)+".class";
        try {
            in=new FileInputStream(path);
            out=new ByteArrayOutputStream();
            byte[] buffer=new byte[2048];
            int len=0;
            while((len=in.read(buffer))!=-1){
                out.write(buffer,0,len);
            }
            return out.toByteArray();
        }
        catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        finally{
            in.close();
            out.close();
        }
        return null;
    }

}
UserClassLoader

呼叫,其中D:\\wp目錄下為.class檔案,裡面有個Son.class

//自定義類載入器的載入路徑
UserClassLoader myClassLoader=new UserClassLoader("D:\\wp");
//包名+類名
Class sonClass=myClassLoader.loadClass("com.Son");

if(sonClass!=null){
    Object obj=sonClass.newInstance();
    System.out.println(sonClass.getClassLoader().toString());
}

三、物件例項化

從位元組碼、執行步驟兩個方面分析

1、位元組碼方面:

  Object object = new Object();,通過javap -verbose -p檢視物件建立的位元組碼指令:

   

  (1)new:如果找不到Class物件就進行類載入,然後分配記憶體(本類路徑上所有的屬性都分配),其中物件的引用也是個變數也佔記憶體(4個位元組),這個指令執行完畢會把物件的壓入虛擬機器棧頂。

  (2)dup:在棧頂複雜引用,如果<init>有引數,把引數壓入操作棧,兩個引用,壓入棧底的用來賦值或儲存到區域性變量表中,棧頂引用作為控制代碼呼叫相關方法。

  (3)invokespecial:呼叫物件例項化方法,通過棧頂方法呼叫<init>方法(也就是呼叫構造方法)。

2、執行步驟

  (1)確認類元資訊是否存在:接到new指令時,在metaspace檢查類元資訊是否存在,沒有就在雙親委派模式下進行類載入,生成Class物件。

  (2)分配物件記憶體:首先計算物件佔用空間大小(成員變數是引用變數就分配4個位元組大小的變數空間),在堆中劃分記憶體空間給新物件(分配空間需要進行同步操作,如:cas)。

  (3)設定預設值:成員變數設定不同形式的0值;

  (4)設定物件頭:設定物件的雜湊碼、鎖資訊、物件所屬的類元資訊,設定取決於JVM。

  (5)執行init方法:初始化成員變數,執行程式碼塊,呼叫類的構造方法,把堆物件首地址賦值給引用變數。

 四、思考:ClassLoader.loadClasshe和Class.forName區別

1 //1
2 Class<Son> sonClass = Son.class;
3 //2
4 Class<?> aClass = Maint.class.getClassLoader().loadClass("com.Son");
5 //3
6 Class<?> bClass = Class.forName("com.Son");

程式碼2:

        其實這種方法調運的是:ClassLoader.loadClass(name, false)方法
        引數一:name,需要載入的類的名稱
        引數二:false,這個類載入以後是否需要去連線(不需要linking)

程式碼:3:
        其實這種方法調運的是:Class.forName(className, true, ClassLoader.getCallerClassLoader())方法
        引數一:className,需要載入的類的名稱。
        引數二:true,是否對class進行初始化(需要initialize)
        引數三:classLoader,對應的類載入器

其中1、2都是將.class檔案載入到JVM中,得到Class物件,但是還沒有連線(Link)靜態程式碼塊不會執行;
程式碼3得到Class物件並且已經完成初始化<clinit>,靜態程式碼塊會執行。( Class.forName("com.mysql.jdbc.Driver");//通過這種方式將驅動註冊到驅動管理器上)。

1、2、3都不會執行物件的建構函式

 五、思考:<clinit>和<init>區別

<clinit>是類(Class)初始化執行的方法,<init>是物件初始化執行的方法(建構函式);

 詳情見:JVM思考-init和clinit區別