類的初始化過程

非法向前引用
編譯器手機的順序是由語句在源文件中出現(xiàn)的順序決定的,靜態(tài)語句塊中只能訪問到定義在靜態(tài)語句之前的變量,定義它之后的變量,可以賦值,但不能訪問
public class Test{
static{
i=0;
system.out.print(i);//非法向前引用
}
static int i=1;
}
(類構造器方法):在jvm第一次加載class文件時調(diào)用,如果類或者接口沒有靜態(tài)語句塊,也沒有對變量的賦值,那么編譯器可以不為這個類生成方法并且他被加鎖了,既不能做耗時操作。 Tips:如果在此方法中耗時很長,就可能造成多個進程阻塞;
類加載的時機
加載
加載與連接階段的驗證動作是交叉進行的
連接
驗證
- 文件格式驗證。是否符合Class文件格式的規(guī)范
- 語義分析。父類,抽象類,接口等。
- 字節(jié)碼驗證
- 符號引用驗證
準備
正式為類變量分配內(nèi)存并設置類變量初始值的階段
public static int value=123;
//final的話 準備階段既123;
//非常量的 static 則準備階段是0;<clinit>類構造方法執(zhí)行才會變成123
解析
可選的,loadClass第二個參數(shù)來判定是否需要解釋。這里的解釋是根據(jù)勒種的符號引用查找相應的實體,在把符號引用替換成一個直接引用的過程。
初始化
使用
卸載
類什么時候才被初始化
只有這6中情況才會導致類的類的初始化
- 創(chuàng)建類的實例,也就是new一個對象
- 訪問某個類或接口的靜態(tài)變量,或者對該靜態(tài)變量賦值
- 調(diào)用類的靜態(tài)方法
- 反射(Class.forName("com.lyj.load"))
- 初始化一個類的子類(會首先初始化子類的父類)
- JVM啟動時標明的啟動類,即文件名和類名相同的那個類
所有引用類的方法都不會觸發(fā)初始化,稱為被動引用。
類引用父類的靜態(tài)字段,不會導致該類被初始化
類的初始化步驟:
- 如果這個類還沒有被加載和鏈接,那先進行加載和鏈接
- 假如這個類存在直接父類,并且這個類還沒有被初始化(注意:在一個類加載器中,類只能初始化一次),那就初始化直接的父類(不適用于接口)
- 加入類中存在初始化語句(如static變量和static塊),那就依次執(zhí)行這些初始化語句。
雙親委派模型
Java虛擬器角度僅僅有兩種不同的類加載器:
一種啟動類加載器(Bootstrap ClassLoader):C++語言實現(xiàn)是虛擬器自身的一部分;
另一種是所有其他的類加載器(java語言,JVM之外 繼承ClassLoader)
更詳細:

Bootstrap ClassLoader:負責加載$JAVA_HOME中jre/lib/rt.jar里所有的class既核心API(ExtClassLoader和AppClassLoader也在此時被加載),由C++實現(xiàn),不是ClassLoader子類
Extension ClassLoader:負責加載java平臺中擴展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目錄下的jar包
App ClassLoader:負責記載classpath中指定的jar包及目錄中class.可以通過getSystemClassLoader()方法獲得
Custom ClassLoader:屬于應用程序根據(jù)自身需要自定義的ClassLoader,如tomcat、jboss都會根據(jù)j2ee規(guī)范自行實現(xiàn)ClassLoader
加載過程中會先檢查類是否被已加載,檢查順序是自底向上,從Custom ClassLoader到BootStrap ClassLoader逐層檢查,只要某個classloader已加載就視為已加載此類,保證此類只所有ClassLoader加載一次。而加載的順序是自頂向下,也就是由上層來逐層嘗試加載此類。
為什么這么設計?
類加載器:任何一個類都需要加載它的類加載器和這個類一同確立其在java虛擬機唯一性。每個類加載器都有類名稱空間。
兩個類是否相同,是由同一個類加載器為前提下才有意義.相同是指equals、instanceof isAssignalbeFrom isIntance等;
例如類java.lang.Object,他存放在rt.jar中,無論哪個類加載器加載這個類,最終都是委派給魔性最頂端的啟動類加載器進行加載。因此Object類在程序的各種類加載器環(huán)境中都是同一個類。 相反如果沒有使用,各個類加載自行加載的話。那么系統(tǒng)將出現(xiàn)多個不同的Object類,那么java類型體系中最基本的行為也無法保證;
以下是ClassLoader的源碼,實現(xiàn)很簡單
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之后 已經(jīng)不提倡用戶覆蓋loadClass方法了,而是應當在自己的類加載邏輯寫到findClass()中。因為loadClass()方法如果加載失敗就會調(diào)用自己的findClass()去加載。這樣就能保證 寫出來的類加載器是符合雙親委派模式的
如果依賴特定的擴展包,需要繼承特定的classLoader嗎?
下面的默認構造器, 要在父親是AppClassLoader的基礎上 加載自己的類。不然會破壞雙親的 總結:新的ClassLoader需要舊的去委托。如果不這樣就會導致在同一個類出現(xiàn)在不同的ClassLoader中。
protected ClassLoader() {
//getSystemClassLoader()其實就是AppClassLoader
this(checkCreateClassLoader(), getSystemClassLoader());
}
如果類已經(jīng) 加載過了,那么應該在哪里存儲 下一次去驗證是否加載過呢?
可以通過該ClassLoader中 protect findLoadedClass(name)方法找到。
resolveClass 什么時候使用
類加載過程總結
- 得到類的原始字節(jié)數(shù)組byte[]。
- findClass里通過defineClass(name, buf, 0, buf.length) 完成類加載。
舉例分析流程
如果一個非ClassPath目錄下的新的數(shù)據(jù)流類通過新的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
}
歡迎大家加入粉絲群:963944895,群內(nèi)免費分享Spring框架、Mybatis框架SpringBoot框架、SpringMVC框架、SpringCloud微服務、Dubbo框架、Redis緩存、RabbitMq消息、JVM調(diào)優(yōu)、Tomcat容器、MySQL數(shù)據(jù)庫教學視頻及架構學習思維導圖