阿里P7帶你深入理解Java虛擬機總結——類初始化過程

類的初始化過程

image.png

非法向前引用

編譯器手機的順序是由語句在源文件中出現(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)

更詳細:


image.png

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ù)庫教學視頻及架構學習思維導圖

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

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

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