jvm類加載機制

首先舉個例子
final static變量會在jvm啟動的時候編譯好
final 實例變量會在獲取實例的時候被初始化好

class Grandpa {
    static {
        System.out.println("爺爺在靜態(tài)代碼塊");
    }
}

class Father extends Grandpa {
    static {
        System.out.println("爸爸在靜態(tài)代碼塊");
    }

    public static int factor = 25;

    public Father() {
        System.out.println("我是爸爸~");
    }
}

class Son extends Father {

    public static int factor2 = 25;

    static {
        System.out.println("兒子在靜態(tài)代碼塊");
    }


    public Son() {
        System.out.println("我是兒子~");
    }
}

public class InitializationDemo {
    public static void main(String[] args) {

        System.out.println("爸爸的歲數(shù):" + Son.factor);    //入口
    }
}
Son.factory 首先會調(diào)用父類的類構造器(<clinit>),執(zhí)行靜態(tài)代碼快和靜態(tài)方法,但是因為調(diào)用的是父類的屬性factor,所以Son自己不會調(diào)用類構造器
所以這邊順序是爺爺在靜態(tài)代碼塊,爸爸在靜態(tài)代碼塊,factor (執(zhí)行了這個)
如果我們換成System.out.println("爸爸的歲數(shù):" + Son.factor2); ,執(zhí)行結果就是,爺爺在靜態(tài)代碼塊,爸爸在靜態(tài)代碼塊,factor (執(zhí)行了這個),factor 2,兒子在靜態(tài)代碼塊
類加載的時機
  • 類的生命周轉(zhuǎn):加載,驗證,準備,解析,初始化,使用和卸載。其中驗證,準備和解析可以成為連接。
  • 加載,驗證,準備,初始化和卸載他們的順序是固定的,即初始化必須在卸載后面。
  • 解析有可能在初始化之后
  • 對于什么時候加載類,jvm并沒有規(guī)定,但是jvm規(guī)定了初始化,而初始化則必定代表已經(jīng)加載。

初始化的四種情況

  • 1.遇到new,獲取或者設置靜態(tài)屬性(final屬性除外),或者調(diào)用靜態(tài)方法時候如果這個類沒有進行過初始化,則觸發(fā)器初始化。

  • 2.是用來reflect包的方法對類進行反射調(diào)用 也會觸發(fā)初始化

  • 3.如果一類需要初始化,但是其父類沒有初始化,則先初始化其父類

  • 4.虛擬機會初始化執(zhí)行main方法的主類

  • 注意如果只是定義了某個類的數(shù)組也不會初始化這個類

  • 接口是無法定義靜態(tài)代碼塊的,但是jvm還是會為接口生成類構造器,用于執(zhí)行靜態(tài)變量

  • 接口初始化的時候并不會初始化父類接口

類加載的過程--加載,驗證,準備,解析和初始化
加載
  • 通過類的全限定名來獲取定義此類的二進制字節(jié)流(即把class文件轉(zhuǎn)化為二進制流)
  • 將這個字節(jié)流所代表的的靜態(tài)存儲結構轉(zhuǎn)化為方法區(qū)的運行時數(shù)據(jù)結構(把類的相關信息放入方法區(qū))
  • 在堆中生成一個class對象,作為方法區(qū)這些數(shù)據(jù)的訪問入口
驗證
  • 確保class文件的字節(jié)流包含的信息符合虛擬機的要求,且不會危害虛擬機自身的安全
  • 主要檢驗文件格式,元數(shù)據(jù),字節(jié)碼驗證和符號引用驗證
  • 文件格式:驗證是否符合class文件格式規(guī)范,以及當前的jvm是否可以處理這個版本的jvm
  • 元數(shù)據(jù)驗證:類是否有父類(除了object),類的父類是否寄出了不允許被寄出的類,是否實現(xiàn)了抽象類的方法等。主要是java語義的驗證
  • 字節(jié)碼驗證:保證操作數(shù)棧的數(shù)據(jù)類型與指令代碼序列能配合工作(比如放了一個int數(shù)據(jù)在操作數(shù)棧,但是卻按照ling類型來加載到本地變量表),保證跳轉(zhuǎn)指令不會跳轉(zhuǎn)到方法體以外的字節(jié)碼指令
  • 符號引用驗證:即將符號引用轉(zhuǎn)換為直接引用的時候發(fā)生的驗證,驗證符號引用字符串描述的全限定名是否能找到對應的類,在指定類中是否存在符合方法的字段描述符以及簡單名稱所描述的方法和字段,符號引用的類和字段以及方法的訪問性是否可悲當前類訪問。
準備
  • 為類變量分配內(nèi)存并設置類變量的初始值(該初始值是默認初始值而不是我們程序?qū)懙闹当热鐂tatic int a=3 那么a被賦值為0 只有該類初始化的時候 a被賦值3)
  • final值會在編譯階段直接變?yōu)閏onstantValue,在準備階段直接被賦值為程序設置的值
解析
  • 就是將常量池的符號引用替換為直接引用的過程。
  • 符號引用是以一組符號來描述所引用的目標,符號引用可以是任何形式的字面量,只要可以定位到目標即可,所引用的目標并不一定已經(jīng)架子啊到內(nèi)存中
  • 直接引用:是直接指向目標的指針、相對偏移量或是一個能間接定位到目標的句柄
  • 虛擬機只是規(guī)定了13個字節(jié)碼執(zhí)行前符號引用需要解析成對應的直接引用,所以符號引用解析的工作可以在類加載的時候或者13個字節(jié)碼執(zhí)行前執(zhí)行
  • 13個字節(jié)碼:anewarry,checkcast ,getfield,getstatic,instanceof,inokeinterface,invokespecial,invokestatic,invokevirtual,multianewarry,new,putfield和putstatic
  • 虛擬機可能會對符號引用的第一次解析結果進行緩存,即在運行常量池記錄直接引用,把常量標識位已解析,所以如果第一次解析失敗了后續(xù)的同樣解析可能會發(fā)揮同樣的異常
初始化
  • 是執(zhí)行類構造器的<clinit>方法
  • 如果類中沒有靜態(tài)變量和靜態(tài)代碼塊
  • 該方法執(zhí)行會加鎖,避免多線程同時初始化該類
類加載器
  • 確保類的唯一性就是classloader+類的全限定名

整個loadclass 就是按照雙親委派機制干活的
resolve=true的時候就是會去初始化類,而我們loadclass 不允許初始化類
jvm規(guī)定了四種初始化類的情況

 protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
        檢測jvm中這個classloader和其父類中的命名空間是否已經(jīng)包含該名稱的class
            Class<?> c = findLoadedClass(name);
            如果沒有開始按照雙親委派機制去加載類
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                首先查看父類是否存在,如果存在就調(diào)用父類的loadClass
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                    否則有可能是頂級加載器bootStrap
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }
如果還未加載到就嘗試自己去加載類
                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);
                 更新下加載的耗時和次數(shù)等
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            這邊至今不太理解,好像是jvm類加載的鏈接那一步,即驗證,鏈接,解析
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

三種類加載器

  • 啟動類加載器:bootstrap classloader,加載java_home/lib下面的目錄或者通過jvm參數(shù)-Xboostrapclasspath指定加載路徑,但是名稱必須是rt.jar。且該classloader無法被引用
  • 擴展類加載器:extension classloader 屬于Launcher類中的ExtClassLoader,繼承了URLClassLoader。加載java_home/lib/ext下面的目錄或被系統(tǒng)參數(shù)java.ext.dirs指定的目錄下所有jar包,不需要指定名稱
  • 應用程序類加載器:application classloader 屬于Launcher類中的AppClassLoader,繼承了URLClassLoader??梢酝ㄟ^classloader.getSystemClassloader獲取該加載器,所以我們也稱呼其為系統(tǒng)類加載器可以通過java.class.path的系統(tǒng)屬性獲取該目錄下jar進行加載或者是加載我們classpath下面的類。如果我們沒用在系統(tǒng)中定義過類加載器,那么這就是默認加載器。我們的線程上下文中的加載器也是這個
類的加載有個默認機制 如果我們在某個類使用了 Aclassloader去加載該類,那么在該類中涉及到的其他類加載也默認使用該類

破壞雙親委派加載模式

  • 覆蓋loadclass方法

  • SPI機制問題(類和接口放在rt.jar),各個廠商的實現(xiàn)放在classpath下面,這樣當我們使用某個類似于SPI服務,當JDK規(guī)定的類放在rt.jar,我們采用bootstrap獲取到了類,這個時候我們需要在這個類中獲取各個廠商的實現(xiàn)但是因為其放在classpath下面,而此時bootstrap無法委派子類去加載,所以只能通過線程上下文的類加載器去加載類,從而得到廠商的class。主要是創(chuàng)建一個ServiceLoader,傳遞一個我們需要尋找的Class以及線程上下文其就會去META-INF/services/接口全限定的文件下面 得到實現(xiàn)類的的class。
    jdbc就是利用serviceLoader各個實現(xiàn)類通過serviceloader加載(創(chuàng)建serviceloader 可以傳遞我們設置的classloader),當各個實現(xiàn)子類被加載的時候會觸發(fā)他們的類構造器執(zhí)行方法即把自身new出來放入drvicermanager中的一個copyonwrite的list中.
    利用serviceloader的好處是我們可以指定類加載器,也可以不需要在classpath下面迭代尋找所有類,只需要去文件中找尋即可發(fā)現(xiàn)實現(xiàn)子類。
    類 com.example.Outer引用了類com.example.Inner,則由類 com.example.Outer的定義加載器負責啟動類 com.example.Inner的加載過程。即加載Outer的加載器會按照雙親委派機制去加載Inner,這也就是為什么我們?nèi)绻褂胐riverManager直接去加載我們的com.mysql.driver。會出現(xiàn)classnotfoundexception,所以采用線程上下文

  • osgi

為什么dubbo不采用jdk的spi

  • jdk的spi會在一次實例化所有實現(xiàn),可能會比較耗時,而且有些可能用不到的實現(xiàn)類也會實例化,浪費資源而且沒有選擇。也可以不在文件中配置具體的實現(xiàn)類
  • 另外dubbo的spi增加了對擴展點IOC和AOP的支持,一個擴展點可以直接setter注入其他擴展點。這是jdk spi不支持的。
  • dubbo中的spi 是一個文件中可以支持多個接口和實現(xiàn) 以key=value而jdk則是一個接口 一個文件 如果接口較多則需要多個文件
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • 代碼編譯的結果從本地機器碼轉(zhuǎn)變?yōu)樽止?jié)碼,是存儲格式發(fā)展的一小步,確實編譯語言發(fā)展的一大步。 虛擬機把描述類的數(shù)據(jù)從...
    胡二囧閱讀 1,069評論 0 0
  • JVM類加載機制 概述 類加載過程 加載 通過類的全限定名獲取類的二進制流 將靜態(tài)存儲結構轉(zhuǎn)化為方法區(qū)的運行時數(shù)據(jù)...
    東溪95閱讀 3,232評論 0 15
  • 虛擬機把描述類的數(shù)據(jù)從Class文件加載到內(nèi)存,并對數(shù)據(jù)進行校驗、轉(zhuǎn)換解析和初始化,最終形成可以被虛擬機直接使用的...
    丑人林宗己閱讀 656評論 0 2
  • 所謂類加載機制,就是虛擬機把描述類的數(shù)據(jù)從Class文件加載到內(nèi)存中,并對其進行校驗,轉(zhuǎn)換,分析以及初始化,并最終...
    登高且賦閱讀 1,231評論 0 15
  • 2018/10/30 有多自律就有多自由 【今日天氣】 睛 18-30度 【早睡早起】22:50-7:16 【踐行...
    雨晨_95a8閱讀 90評論 0 0

友情鏈接更多精彩內(nèi)容