JVM之類裝載子系統

返回主博客

類加載子系統

   類加載子系統負責將我們的class文件加載到JVM的內存模型中,在方法區加入他的類信息,在堆中創建對應的java.lang.Class對象。這個類就可以被java程序所使用了。

類加載過程

分爲 加載->鏈接->初始化 三個階段

 

  • 加載

通過類的全限定名,從文件系統(或者網絡等其他方式)加載Class文件,將這些字節流的文件信息,轉化爲運行時的數據結構,生成java.lang.Class對象。作爲方法區這個類的各種數據的訪問入口。

加載的類信息放置在方法區,除了類信息,方法區還會存放運行時常量池(其實就是Class文件的常量池)信息,可能包括字符串字面量和數字常量(這部分常量信息是Class文件中常量池映射)

 

  • 鏈接:

驗證-> 準備 -> 解析 三個階段

驗證(verify)

字節碼合法性:格式,元數據,字節碼,引用符號。

準備(prepare)

爲類變量分配內存以及爲靜態變量的賦默認初始值(即0值) 不包含final + static修飾的,final修飾的在編譯階段就已經賦值,所以準備階段會顯示初始化,而不是賦默認值。不會爲實例變量分配初始化。

解析(Resolve)

將常量池引用(字面量形式)創建爲直接引用

主要針對常量池中的Constant_Class_info,Constant_Fieldref_info,Constant_methodref_info,對接口,類方法,接口方法,方法類型等,對進行解析,將class文件中的這些字面量信息生成直接引用。

invokevirtual的處理。生成虛方法表,便於函數重寫時知道調用哪個類的方法。

事實上解析往往伴隨實例化完成的。

 

  • 初始化:

執行clinit (就是對類的static區的代碼運行)

虛擬機必須保證同一個類的clinit是被加鎖的。

 

補充:類和對象的創建和賦值順序

1、父類和子類的 final static 的基本數據類型賦值(準備階段) ->

    父類static執行或static{}(誰在按順序執行) -> 執行 子類static執行

2、子類對象開始構造 -> 父類對象開始構造 -> 父類對象屬性賦值 -> 子類對象屬性賦值。

 

類加載器

主要有

啓動類加載器(bootstrap ClassLoader),擴展類加載器(extClassLoader),應用類加載器(SystemClassLoader/AppClassLoader)

他們並非繼承關係,這裏說的parent 是 子類加載器會有一個成員變量叫parent的。比如AppClassLoader 的 parent屬性中放的是ExtClassLoader。

 

分爲兩類:

引導類加載器(由C和C++編寫的)

自定義類加載器(所有派生於ClassLoader的都是自定義加載器,比如extClassLoader,AppClassLoader)

代碼案例:拿到ClassLoader.getSystemClassLoader(); 並打印他們的parent

        ClassLoader classLoader = ClassLoader.getSystemClassLoader();
        System.out.println(classLoader);
        //sun.misc.Launcher$AppClassLoader@18b4aac2
        System.out.println(classLoader.getParent());
        //sun.misc.Launcher$ExtClassLoader@85ede7b
        System.out.println(classLoader.getParent().getParent());
        //null
        System.out.println(ClassLoaderTest.class.getClassLoader());
        //sun.misc.Launcher$AppClassLoader@18b4aac2
        System.out.println(String.class.getClassLoader());
        //null
        System.out.println(Integer.class.getClassLoader());
        //null
        System.out.println(ArrayList.class.getClassLoader());
        //null

BootStrapClassLoader是獲取不到的,並且是C和C++編寫的,因此運行結果爲null,他只負責加載java核心類庫中的類,如String,Integer,ArrayList等。

 

  • 引導類加載器(BootStrapClassLoader /啓動類加載器)

C和C++編寫

啓動java核心類庫:jdk的,sun的,resource.jar的

不繼承ClassLoader,無父類加載器(肯定的不是java語言編寫的)

加載擴展類和應用類加載器,並指定爲他們的父類加載器

出於安全BootStrapClassLoader只加載java,javax,sun開頭的(並且這些類即時被我們自定義了,JVM也只會加載jdk中的)

 

  • 虛擬機自帶類加載器(ExtClassLoader)

在cun.misc.Launcher實現,從java.ext.dir目錄中或jdk安裝目錄jre/lib/ext目錄下加載類,如果用戶自定義類在此目錄下,也會由自定義類加載器加載。

  • 虛擬機自帶類加載器(AppClassLoader)

在cun.misc.Launcher實現,負責環境變量或者java.class.path下的類庫,是系統中默認的類加載器。

 可以由ClassLoader.getSystemClassLoader()獲取

 

  • 用戶自定義類加載器

基本使用上面三種類加載器就夠了,但是有些場景是需要的:

1、隔離加載類: 某些框架當中需要使用中間件,中間件和應用模塊隔離的,需要將類加載到不同環境當中,確保應用jar包和中間件的不衝突。比如中間件有自己的jar,但是可能會有類的衝突。很多主流框架都會自定義類加載器。

2、修改類的加載方式。

3、擴展加載源,可能考慮從其他來源加載類

4、防止源碼泄露,可以對字節碼加密。解密的時候可以用自定義類加載器。

 

  • 如何自定義類加載器?

繼承ClassLoader或者URLClassLoader

1.2之前可以重寫loadClass,之後建議findClass配合defineClass實現。

 

  • 關於ClassLoader

主要接口

獲取ClassLoader的方式

clazz.getClassLoader() /Class.forName("aaa/bbb.XXX").geClassLoader;

Thread.currentThread().getContextClassLoader()

ClassLoader.getSystemClassLoader()

DriverManager.getCallderClassLoader()

 

雙親委派機制

按需加載,需要用到才加載,是一種任務委派模式:

  1. 如果一個類收到類加載請求,他不會立即加載,而是交給其父類加載器,
  2. 如果父類加器還存在父類加器,則進一步委派,依次遞歸。一直到引導類加載器。
  3. 如果父類加載器可以完成類加載則加載返回,否則交給子類。

例子1

如果我們自定義在java.lang下建一個String類,還是會加載核心類庫的String防止惡意攻擊,因爲委託到BootStrapClassLoader的時候會識別到其爲java.lang下面的。如果在這個自定義的類下面運行main方法,便找不到這個方法,因爲加載的是核心類庫下面的String

例子2、

我們實現rt.jar下面的接口,那麼接口由BootStrapClassLoader加載,而實現類由AppClassLoader加載。

 

雙親委派優勢

  1. 避免重複加載(比如有繼承時)
  2. 防止惡意攻擊

體現了沙箱安全機制 :將對核心類庫的保護,成爲沙箱安全機制

 

補充

判斷java中兩個Class對象是不是同一個類的條件:

  1. 類全限類名一樣
  2. ClassLoader是一樣的

JVM必須知道一個類是由啓動類加載器加載的還是啓動類加載加載的,如果一個類是用戶類加載器加載的,JVM會將類加載器的引用作爲類的一部分信息保存在方法區當中,當解析一個類型到另一個類型引用的時候,JVM則必須保證兩個類加載器是相同的。(動態鏈接)

 

JVM中對類的使用分爲主動和被動:(主要看有沒有調用其clinit方法)

主動創建方式:

  1. 創建一個類的實例
  2. 訪問其靜態變量
  3. 訪問其靜態方法
  4. 反射
  5. 初始化其子類
  6. 表明爲啓動類的

1.7提供的動態語言。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章