類加載子系統
類加載子系統負責將我們的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
如果我們自定義在java.lang下建一個String類,還是會加載核心類庫的String防止惡意攻擊,因爲委託到BootStrapClassLoader的時候會識別到其爲java.lang下面的。如果在這個自定義的類下面運行main方法,便找不到這個方法,因爲加載的是核心類庫下面的String
例子2、
我們實現rt.jar下面的接口,那麼接口由BootStrapClassLoader加載,而實現類由AppClassLoader加載。
雙親委派優勢
- 避免重複加載(比如有繼承時)
- 防止惡意攻擊
體現了沙箱安全機制 :將對核心類庫的保護,成爲沙箱安全機制
補充
判斷java中兩個Class對象是不是同一個類的條件:
- 類全限類名一樣
- ClassLoader是一樣的
JVM必須知道一個類是由啓動類加載器加載的還是啓動類加載加載的,如果一個類是用戶類加載器加載的,JVM會將類加載器的引用作爲類的一部分信息保存在方法區當中,當解析一個類型到另一個類型引用的時候,JVM則必須保證兩個類加載器是相同的。(動態鏈接)
JVM中對類的使用分爲主動和被動:(主要看有沒有調用其clinit方法)
主動創建方式:
- 創建一個類的實例
- 訪問其靜態變量
- 訪問其靜態方法
- 反射
- 初始化其子類
- 表明爲啓動類的
1.7提供的動態語言。