https://luhaoaimama1.github.i...java
類的初始化過程git
非法向前引用github
編譯器手機的順序是由語句在源文件中出現的順序決定的,靜態語句塊中只能訪問到定義在靜態語句以前的變量,定義它以後的變量,能夠賦值,但不能訪問bootstrap
public class Test{數組
static{tomcat
i=0;jvm
system.out.print(i);//非法向前引用this
}spa
static int i=1;設計
}
(類構造器方法):在jvm第一次加載class文件時調用,若是類或者接口沒有靜態語句塊,也沒有對變量的賦值,那麼編譯器能夠不爲這個類生成方法而且他被加鎖了,既不能作耗時操做。 Tips:若是在此方法中耗時很長,就可能形成多個進程阻塞;
類加載的時機
加載
加載與鏈接階段的驗證動做是交叉進行的
鏈接
驗證
文件格式驗證。是否符合Class文件格式的規範
語義分析。父類,抽象類,接口等。
字節碼驗證
符號引用驗證
準備
正式爲類變量分配內存並設置類變量初始值的階段
public static int value=123;
//final的話 準備階段既123;
//很是量的 static 則準備階段是0;<clinit>類構造方法執行纔會變成123
解析
可選的,loadClass第二個參數來斷定是否須要解釋。這裏的解釋是根據勒種的符號引用查找相應的實體,在把符號引用替換成一個直接引用的過程。
初始化
使用
卸載
類何時才被初始化
只有這6中狀況纔會致使類的類的初始化
建立類的實例,也就是new一個對象
訪問某個類或接口的靜態變量,或者對該靜態變量賦值
調用類的靜態方法
反射(Class.forName("com.lyj.load"))
初始化一個類的子類(會首先初始化子類的父類)
JVM啓動時標明的啓動類,即文件名和類名相同的那個類
全部引用類的方法都不會觸發初始化,稱爲被動引用。
類引用父類的靜態字段,不會致使該類被初始化
類的初始化步驟:
若是這個類尚未被加載和連接,那先進行加載和連接
假如這個類存在直接父類,而且這個類尚未被初始化(注意:在一個類加載器中,類只能初始化一次),那就初始化直接的父類(不適用於接口)
加入類中存在初始化語句(如static變量和static塊),那就依次執行這些初始化語句。
雙親委派模型
Java虛擬器角度僅僅有兩種不一樣的類加載器:
一種啓動類加載器(Bootstrap ClassLoader):C++語言實現是虛擬器自身的一部分;
另外一種是全部其餘的類加載器(java語言,JVM以外 繼承ClassLoader)
更詳細:
!
Bootstrap ClassLoader:負責加載$JAVA_HOME中jre/lib/rt.jar裏全部的class既核心API(ExtClassLoader和AppClassLoader也在此時被加載),由C++實現,不是ClassLoader子類
Extension ClassLoader:負責加載java平臺中擴展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目錄下的jar包
App ClassLoader:負責記載classpath中指定的jar包及目錄中class.能夠經過getSystemClassLoader()方法得到
Custom ClassLoader:屬於應用程序根據自身須要自定義的ClassLoader,如tomcat、jboss都會根據j2ee規範自行實現ClassLoader
加載過程當中會先檢查類是否被已加載,檢查順序是自底向上,從Custom ClassLoader到BootStrap ClassLoader逐層檢查,只要某個classloader已加載就視爲已加載此類,保證此類只全部ClassLoader加載一次。而加載的順序是自頂向下,也就是由上層來逐層嘗試加載此類。
爲何這麼設計?
類加載器:任何一個類都須要加載它的類加載器和這個類一同確立其在java虛擬機惟一性。每一個類加載器都有類名稱空間。
兩個類是否相同,是由同一個類加載器爲前提下才有意義.相同是指equals、instanceof isAssignalbeFrom isIntance等;
例如類java.lang.Object,他存放在rt.jar中,不管哪一個類加載器加載這個類,最終都是委派給魔性最頂端的啓動類加載器進行加載。所以Object類在程序的各類類加載器環境中都是同一個類。 相反若是沒有使用,各個類加載自行加載的話。那麼系統將出現多個不一樣的Object類,那麼java類型體系中最基本的行爲也沒法保證;
如下是ClassLoader的源碼,實現很簡單
rotected synchronized Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// First, check if the class has already been loaded
Class c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
//從父加載器加載
c = parent.loadClass(name, false);
} else {
//從bootstrap loader加載
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.
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
其餘思考的問題
類加載的邏輯應該在哪裏寫?
jdk 1.2以後 已經不提倡用戶覆蓋loadClass方法了,而是應當在本身的類加載邏輯寫到findClass()中。由於loadClass()方法若是加載失敗就會調用本身的findClass()去加載。這樣就能保證 寫出來的類加載器是符合雙親委派模式的
若是依賴特定的擴展包,須要繼承特定的classLoader嗎?
下面的默認構造器, 要在父親是AppClassLoader的基礎上 加載本身的類。否則會破壞雙親的 總結:新的ClassLoader須要舊的去委託。若是不這樣就會致使在同一個類出如今不一樣的ClassLoader中。
protected ClassLoader() {
//getSystemClassLoader()其實就是AppClassLoader
this(checkCreateClassLoader(), getSystemClassLoader());
}
若是類已經 加載過了,那麼應該在哪裏存儲 下一次去驗證是否加載過呢?
能夠經過該ClassLoader中 protect findLoadedClass(name)方法找到。
resolveClass 何時使用
類加載過程總結
獲得類的原始字節數組byte[]。
findClass裏經過defineClass(name, buf, 0, buf.length) 完成類加載。
舉例分析流程
若是一個非ClassPath目錄下的新的數據流類經過新的ClassLoader(NewClassLoader)去加載。
Parent:
NewClassLoader-AppClassLoader->ExtClassLoader
第一次初始化
loadClass:每次findLoadedClass都找不到
NewClassLoader-AppClassLoader->ExtClassLoader
findClass:(loadClass的逆序)
ExtClassLoader->AppClassLoader->NewClassLoader(最後在此define 生成類後loadClass一次)
第二次初始化
loadClass:第一個NewClassLoader中findLoadedClass就找到了
NB技巧:子類能夠公開父類中的protected的方法;
public void findClass_(){
super.findClass();//protected
}
Demo Code