JVM總括四-類加載過程、雙親委派模型、對象實例化過程

JVM總括四-類加載過程、雙親委派模型、對象實例化過程


目錄:JVM總括:目錄html

1、 類加載過程

類加載過程就是將.class文件轉化爲Class對象,類實例化的過程,(User user = new User(); 這個過程是對象實例化的過程);java

一個.class文件只有一個Class對象(字節碼對象),能夠有無數個對象(例如:new User(););mysql

 

一、Load:web

  將編譯後的.class文件以二進制流的方式加載到JVM內存中,並轉化爲特定的數據結構,用到的就是classLoad二類加載器。這個過程當中校驗cafe babe魔法數常量池文件長度是否有父類等。sql

  其實加載階段用一句話來描述就是:把代碼數據加載到內存中。數據庫

二、Link:bootstrap

  分爲驗證、準備、解析三步。windows

  驗證:更爲詳細的驗證,好比:final是否規範(二次賦值不規範)、static是否合理(靜態方法必須引用靜態變量)、類型是否正確數據結構

  準備:當完成字節碼文件的校驗以後,JVM 便會開始爲類變量分配內存並初始化。這裏須要注意兩個關鍵點,即內存分配的對象以及初始化的類型。框架

    內存分配的對象。Java 中的變量有「類變量」和「類成員變量」兩種類型,「類變量」指的是被 static 修飾的變量,而其餘全部類型的變量都屬於「類成員變量」。

    在準備階段,JVM 只會爲「類變量」分配內存,而不會爲「類成員變量」分配內存。「類成員變量」的內存分配須要等到初始化階段纔開始。

    好比下面的代碼在準備階段,只會爲 age 屬性分配內存,而不會爲 website 屬性分配內存。

    public static int age= 3;

    public String website = "www.beijingdesigner.com";

    例以下面的代碼在準備階段以後,age的值將是 0,而不是 3。

    public static int age= 3;

    但若是一個變量是常量(被 static final 修飾)的話,那麼在準備階段,屬性便會被賦予用戶但願的值。例以下面的代碼在準備階段以後,number 的值將是 3,而不是 0。

    public static final int number = 3;

    這其中的道理咱們想下就明白了。

    兩個語句的區別是一個有 final 關鍵字修飾,另一個沒有。被final修飾的常量,是不容許再被改變的。因此初始化就是3了。

  解析:把類中的符號引用轉化爲直接引用,完成內存結構佈局。(符號引用轉化爲直接引用:例如:test1() { test2(); },這裏test1調用test2方法就是符號引用,但實際test2()經過一個指針指向test2方法的內存地址,這個指針負責調用,它就是直接引用)。

三、Init:

  執行類構造器<clinit>,遞歸初始化父類的靜態代碼塊執行構造函數。(先執靜態變量,再執行行靜態代碼塊)。

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

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

2、類加載的原則:雙親委派模型

  類加載是怎麼肯定類文件的位置呢?

  一、Bootstrap:最高級的類加載器,裝置最核心的類,如:Object、System、String;

  二、Extension ClassLoader:JDK9以前的類加載器,之後的爲Platform ClassLoader,加載系統的擴展類;

  三、Application ClassLoader:應用類加載器,主要加載用戶自定義CLASSPATH路徑下的類。

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

其中第2、第三層爲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);
----------------------------------- sun.misc.Launcher$AppClassLoader@18b4aac2 sun.misc.Launcher$ExtClassLoader@5acf9800
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

 自定義類加載器的狀況:

一、隔離加載類:框架中吧類加載到不一樣的環境中

二、修改類加載方式:除了Bootstrap,其餘類並不是必定要加入。

三、擴展加載源:好比加載數據庫;

四、防止源碼泄露:能夠對類進行加密,那加載的時候須要自定義加載器對類進行解密加載。

自定義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());
}

3、對象實例化

從字節碼、執行步驟兩個方面分析

一、字節碼方面:

  Object object = new Object();,經過javap -verbose -p查看對象建立的字節碼指令:

   

  (1)new:若是找不到Class對象就進行類加載,而後分配內存(本類路徑上全部的屬性都分配),其中對象的引用也是個變量也佔內存(4個字節),這個指令執行完畢會把對象的壓入虛擬機棧頂。

  (2)dup:在棧頂複雜引用,若是<init>有參數,把參數壓入操做棧,兩個引用,壓入棧底的用來賦值或保存到局部變量表中,棧頂引用做爲句柄調用相關方法。

  (3)invokespecial:調用對象實例化方法,經過棧頂方法調用<init>方法(也就是調用構造方法)。

二、執行步驟

  (1)確認類元信息是否存在:接到new指令時,在metaspace檢查類元信息是否存在,沒有就在雙親委派模式下進行類加載,生成Class對象。

  (2)分配對象內存:首先計算對象佔用空間大小(成員變量是引用變量就分配4個字節大小的變量空間),在堆中劃份內存空間給新對象(分配空間須要進行同步操做,如:cas)。

  (3)設定默認值:成員變量設置不一樣形式的0值;

  (4)設置對象頭:設置對象的哈希碼、鎖信息、對象所屬的類元信息,設置取決於JVM。

  (5)執行init方法:初始化成員變量,執行代碼塊,調用類的構造方法,把堆對象首地址賦值給引用變量。

 4、思考: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,對應的類加載器

其中一、2都是將.class文件加載到JVM中,獲得Class對象,可是尚未鏈接(Link)靜態代碼塊不會執行;
代碼3獲得Class對象而且已經完成初始化<clinit>,靜態代碼塊會執行。( Class.forName("com.mysql.jdbc.Driver");//經過這種方式將驅動註冊到驅動管理器上)。

一、二、3都不會執行對象的構造函數

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

<clinit>是類(Class)初始化執行的方法,<init>是對象初始化執行的方法(構造函數);

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

相關文章
相關標籤/搜索