深入淺析Java類加載機(jī)制

類加載器簡(jiǎn)單來說是用來加載 Java 類到 Java 虛擬機(jī)中的罪帖。Java 虛擬機(jī)使用 Java 類的方式如下:Java 源程序(.java 文件)在經(jīng)過 Java 編譯器編譯之后就被轉(zhuǎn)換成 Java 字節(jié)代碼(.class 文件)。類加載器負(fù)責(zé)讀取 Java 字節(jié)代碼邮屁,并轉(zhuǎn)換成 java.lang.Class類的一個(gè)實(shí)例整袁。每個(gè)這樣的實(shí)例用來表示一個(gè) Java 類。通過此實(shí)例的 newInstance()方法就可以創(chuàng)建出該類的一個(gè)對(duì)象佑吝。

想要真正深入理解Java類加載機(jī)制坐昙,就要弄懂三個(gè)問題:類什么時(shí)候加載、類加載的過程是什么芋忿、用什么加載炸客。所以本文分為三部分分別介紹Java類加載的時(shí)機(jī)疾棵、類加載的過程、加載器痹仙。

一是尔、Java類加載的時(shí)機(jī)

1.1 類加載的生命周期
類加載的生命周期是從類被加載到內(nèi)存開始,直到卸載處內(nèi)存為止的蝶溶。整個(gè)生命周期分為7個(gè)階段:加載嗜历、驗(yàn)證、準(zhǔn)備抖所、解析梨州、初始化、使用田轧、卸載暴匠。其中,驗(yàn)證傻粘、準(zhǔn)備每窖、解析三部分統(tǒng)稱為連接。具體步驟如下圖所示:


下面簡(jiǎn)單介紹下類加載器所執(zhí)行的生命周期的過程弦悉。
(1) 裝載:查找和導(dǎo)入Class文件窒典;

(2) 鏈接:把類的二進(jìn)制數(shù)據(jù)合并到JRE中;
(a)校驗(yàn):檢查載入Class文件數(shù)據(jù)的正確性稽莉;
(b)準(zhǔn)備:給類的靜態(tài)變量分配存儲(chǔ)空間瀑志;
(c)解析:將符號(hào)引用轉(zhuǎn)成直接引用;

(3) 初始化:對(duì)類的靜態(tài)變量污秆,靜態(tài)代碼塊執(zhí)行初始化操作劈猪。

1.2 類加載的時(shí)機(jī)
類加載的時(shí)機(jī)Java虛擬機(jī)規(guī)范中并沒有強(qiáng)制規(guī)定,但是對(duì)于初始化階段良拼,有5種場(chǎng)景必須立即執(zhí)行初始化战得,也被稱為主動(dòng)引用。
(1) 遇到new庸推、getstatic常侦、putstatic或invokestatic這4條字節(jié)碼指令時(shí),如果類沒有進(jìn)行過初始化贬媒,則需要先觸發(fā)其初始化刮吧。生成這4條指令的最常見的Java代碼場(chǎng)景是:使用new關(guān)鍵字實(shí)例化對(duì)象的時(shí)候,讀取或設(shè)置一個(gè)類的靜態(tài)字段(被final修飾掖蛤、已在編譯期把結(jié)果放入常量池的靜態(tài)字段除外)的時(shí)候,以及調(diào)用一個(gè)類的靜態(tài)方法的時(shí)候井厌。

(2) 使用java.lang.reflect包的方法對(duì)類進(jìn)行反射調(diào)用的時(shí)候蚓庭,如果類沒有進(jìn)行過初始化致讥,則需要先觸發(fā)其初始化。

(3) 當(dāng)初始化一個(gè)類的時(shí)候器赞,如果發(fā)現(xiàn)其父類還沒有進(jìn)行過初始化垢袱,則需要先觸發(fā)其父類的初始化。

(4)當(dāng)虛擬機(jī)啟動(dòng)時(shí)港柜,用戶需要指定一個(gè)要執(zhí)行的主類(包含main()方法的那個(gè)類)请契,虛擬機(jī)會(huì)先初始化這個(gè)主類。

(5)當(dāng)使用JDK 1.7動(dòng)態(tài)語言支持時(shí)夏醉,如果一個(gè)java.lang.invoke.MethodHandle實(shí)例最后的解析結(jié)果REF_getStatic爽锥、REF_putStatic、REF_invokeStatic的方法句柄畔柔,并且該方法句柄所對(duì)應(yīng)的類沒有初始化過氯夷,則先觸發(fā)初始化。

二靶擦、Java類加載的過程

類加載的全過程分為7個(gè)階段腮考,但是主要的過程是加載、驗(yàn)證玄捕、準(zhǔn)備踩蔚、解析、初始化這5個(gè)階段枚粘。
2.1 加載
在加載階段馅闽,虛擬機(jī)需要完成3件事情:
(1) 通過一個(gè)類的全限定名來獲取定義此類的二進(jìn)制字節(jié)流;

(2) 將這個(gè)字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)赌结;

(3) 在Java堆中生成一個(gè)代表這個(gè)類的java.lang.Class對(duì)象捞蛋,作為方法區(qū)這些數(shù)據(jù)的訪問入口。

2.2 驗(yàn)證
驗(yàn)證階段的目的是為了確保Class文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)的要求柬姚,并且不會(huì)危害虛擬機(jī)自身的安全拟杉。整體來看,驗(yàn)證階段大致分為4個(gè)驗(yàn)證動(dòng)作量承。
(1)文件格式驗(yàn)證
第一階段是驗(yàn)證字節(jié)流是否符合Class文件格式的規(guī)范搬设,并且能被當(dāng)前版本的虛擬機(jī)處理。主要目的是保證輸入的字節(jié)流能正確地解析并存儲(chǔ)于方法區(qū)之內(nèi)撕捍,格式上符合描述一個(gè)Java類型信息的要求拿穴。該階段是基于二進(jìn)制字節(jié)流驗(yàn)證的,只有通過了這個(gè)階段的驗(yàn)證忧风,字節(jié)流才會(huì)進(jìn)入內(nèi)存的方法去中存儲(chǔ)默色,后面的3個(gè)驗(yàn)證都是基于方法區(qū)的存儲(chǔ)結(jié)構(gòu)進(jìn)行的。
這一階段可能的驗(yàn)證點(diǎn):

  • 是否以魔數(shù)開頭狮腿;
  • 主腿宰、次版本號(hào)是否在當(dāng)前虛擬機(jī)處理范圍內(nèi)呕诉;
  • 常量池的常量數(shù)據(jù)類型是否被支持;
  • 吃度。甩挫。。

(2)元數(shù)據(jù)驗(yàn)證
元數(shù)據(jù)驗(yàn)證是對(duì)字節(jié)碼描述信息進(jìn)行語義分析椿每,以保證其描述的信息符合Java語言規(guī)范的要求伊者。這個(gè)階段可能的驗(yàn)證點(diǎn):

  • 是否有父類;
  • 是否繼承了不被允許繼承的類间护;
  • 如果該類不是抽象類亦渗,是否實(shí)現(xiàn)了其父類或接口要求實(shí)現(xiàn)的所有方法;
  • 兑牡。央碟。。

(3)字節(jié)碼驗(yàn)證
字節(jié)碼驗(yàn)證的主要目的是通過數(shù)據(jù)流和控制流分析均函,確定程序語義的合法性和邏輯性亿虽。該階段將對(duì)類的方法體進(jìn)行校驗(yàn)分析,保證被校驗(yàn)類的方法在運(yùn)行時(shí)不會(huì)做出危害虛擬機(jī)安全的事情苞也。這個(gè)階段可能的驗(yàn)證點(diǎn):

  • 保證任何時(shí)候操作數(shù)棧的數(shù)據(jù)類型與指令代碼序列的一致性洛勉;
  • 跳轉(zhuǎn)指令不會(huì)跳轉(zhuǎn)到方法體以外的字節(jié)碼指令上;
  • 如迟。收毫。。

(4)符號(hào)引用驗(yàn)證
符號(hào)引用驗(yàn)證的主要目的是保證解析動(dòng)作能正常執(zhí)行殷勘,如果無法通過符號(hào)引用驗(yàn)證此再,則會(huì)拋出異常。這個(gè)階段可能的驗(yàn)證點(diǎn):

  • 符號(hào)引用的類玲销、字段输拇、方法的訪問性(public、private等)是否可被當(dāng)前類訪問贤斜;
  • 指定類是否存在符合方法的字段描述符策吠;
  • 。瘩绒。猴抹。

2.3 準(zhǔn)備
準(zhǔn)備階段是正式為類變量分配并設(shè)置類變量初始值的階段,這些內(nèi)存都將在方法區(qū)中進(jìn)行分配,需要說明的是:
這時(shí)候進(jìn)行內(nèi)存分配的僅包括類變量(被static修飾的變量),而不包括實(shí)例變量,實(shí)例變量將會(huì)在對(duì)象實(shí)例化時(shí)隨著對(duì)象一起分配在Java堆中;這里所說的初始值“通常情況”是數(shù)據(jù)類型的零值锁荔,例如:

public static int value = 1;

value在準(zhǔn)備階段過后的初始值為0而不是1,而把value賦值的putstatic指令將在初始化階段才會(huì)被執(zhí)行蟀给。

特殊情況:
public static final int value = 1;//此時(shí)準(zhǔn)備階段value賦值為1

2.4 解析
解析階段是虛擬機(jī)將常量池內(nèi)的符號(hào)引用替換成直接引用的過程。直接引用是直接指向目標(biāo)的指針,相對(duì)偏移量或是一個(gè)能間接定位到目標(biāo)的句柄坤溃。直接引用和虛擬機(jī)實(shí)現(xiàn)的內(nèi)存有關(guān)拍霜,同一個(gè)符號(hào)引用在不同虛擬機(jī)實(shí)例上翻譯出來的直接引用不盡相同。

2.5 初始化
初始化階段是類加載過程的最后一步薪介,到了該階段才真正開始執(zhí)行類定義的Java程序代碼,根據(jù)程序員通過代碼定制的主觀計(jì)劃去初始化類變量和其他資源越驻,是執(zhí)行類構(gòu)造器初始化方法的過程汁政。

三、類加載器

類加載器大致可以分為以下3部分:
(1) 啟動(dòng)類加載器: 將存放于<JAVA_HOME>\lib目錄中的缀旁,或者被-Xbootclasspath參數(shù)所指定的路徑中的记劈,并且是虛擬機(jī)識(shí)別的(僅按照文件名識(shí)別,如 rt.jar 名字不符合的類庫(kù)即使放在lib目錄中也不會(huì)被加載)類庫(kù)加載到虛擬機(jī)內(nèi)存中并巍。啟動(dòng)類加載器無法被Java程序直接引用目木。
(2) 擴(kuò)展類加載器 : 將<JAVA_HOME>\lib\ext目錄下的,或者被java.ext.dirs系統(tǒng)變量所指定的路徑中的所有類庫(kù)加載懊渡。開發(fā)者可以直接使用擴(kuò)展類加載器刽射。
(3) 應(yīng)用程序類加載器: 負(fù)責(zé)加載用戶類路徑(ClassPath)上所指定的類庫(kù),開發(fā)者可直接使用。

我們的應(yīng)用程序都是由這三種類加載器相互配合加載的剃执。它們的關(guān)系如下圖所示誓禁,稱之為雙親委派模型。

類加載器雙親委派模型

工作過程:如果一個(gè)類加載器接收到了類加載的請(qǐng)求肾档,它首先把這個(gè)請(qǐng)求委托給他的父類加載器去完成摹恰,每個(gè)層次的類加載器都是如此,因此所有的加載請(qǐng)求都應(yīng)該傳送到頂層的啟動(dòng)類加載器中怒见,只有當(dāng)父加載器反饋?zhàn)约簾o法完成這個(gè)加載請(qǐng)求(它在搜索范圍中沒有找到所需的類)時(shí)俗慈,子加載器才會(huì)嘗試自己去加載。

好處:java類隨著它的類加載器一起具備了一種帶有優(yōu)先級(jí)的層次關(guān)系遣耍。例如類java.lang.Object闺阱,它存放在rt.jar中,無論哪個(gè)類加載器要加載這個(gè)類配阵,最終都會(huì)委派給啟動(dòng)類加載器進(jìn)行加載馏颂,因此Object類在程序的各種類加載器環(huán)境中都是同一個(gè)類。相反棋傍,如果用戶自己寫了一個(gè)名為java.lang.Object的類救拉,并放在程序的Classpath中,那系統(tǒng)中將會(huì)出現(xiàn)多個(gè)不同的Object類瘫拣,java類型體系中最基礎(chǔ)的行為也無法保證亿絮,應(yīng)用程序也會(huì)變得一片混亂。

雙親委派模型實(shí)現(xiàn)起來其實(shí)很簡(jiǎn)單,以下是實(shí)現(xiàn)代碼派昧,通過以下代碼黔姜,可以對(duì)JVM采用的雙親委派類加載機(jī)制有了更感性的認(rèn)識(shí)。

public Class<?> loadClass(String name)throws ClassNotFoundException {
        return loadClass(name, false);
}

protectedsynchronized Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException {
        // 首先判斷該類型是否已經(jīng)被加載
        Class c = findLoadedClass(name);

        if (c == null) {
            //如果沒有被加載蒂萎,就委托給父類加載或者委派給啟動(dòng)類加載器加載
            try {
                if (parent != null) {
                    //如果存在父類加載器秆吵,就委派給父類加載器加載
                    c = parent.loadClass(name, false);
                } else {
                  //如果不存在父類加載器,就檢查是否是由啟動(dòng)類加載器加載的類五慈,通過調(diào)用本地方法
                  native Class findBootstrapClass(String name)
                  c = findBootstrapClass0(name);
                }
            } catch (ClassNotFoundException e) {
              // 如果父類加載器和啟動(dòng)類加載器都不能完成加載任務(wù)纳寂,才調(diào)用自身的加載功能
                c = findClass(name);
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市泻拦,隨后出現(xiàn)的幾起案子毙芜,更是在濱河造成了極大的恐慌,老刑警劉巖争拐,帶你破解...
    沈念sama閱讀 222,183評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件腋粥,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡架曹,警方通過查閱死者的電腦和手機(jī)隘冲,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來音瓷,“玉大人对嚼,你說我怎么就攤上這事∩鳎” “怎么了纵竖?”我有些...
    開封第一講書人閱讀 168,766評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)杏愤。 經(jīng)常有香客問我靡砌,道長(zhǎng),這世上最難降的妖魔是什么珊楼? 我笑而不...
    開封第一講書人閱讀 59,854評(píng)論 1 299
  • 正文 為了忘掉前任凭语,我火速辦了婚禮普筹,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己偶器,他們只是感情好欢伏,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,871評(píng)論 6 398
  • 文/花漫 我一把揭開白布逾礁。 她就那樣靜靜地躺著递惋,像睡著了一般。 火紅的嫁衣襯著肌膚如雪佑惠。 梳的紋絲不亂的頭發(fā)上朋腋,一...
    開封第一講書人閱讀 52,457評(píng)論 1 311
  • 那天齐疙,我揣著相機(jī)與錄音,去河邊找鬼旭咽。 笑死贞奋,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的穷绵。 我是一名探鬼主播轿塔,決...
    沈念sama閱讀 40,999評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼请垛!你這毒婦竟也來了催训?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,914評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤宗收,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后亚兄,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體混稽,經(jīng)...
    沈念sama閱讀 46,465評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,543評(píng)論 3 342
  • 正文 我和宋清朗相戀三年审胚,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了匈勋。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,675評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡膳叨,死狀恐怖洽洁,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情菲嘴,我是刑警寧澤饿自,帶...
    沈念sama閱讀 36,354評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站龄坪,受9級(jí)特大地震影響昭雌,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜健田,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,029評(píng)論 3 335
  • 文/蒙蒙 一烛卧、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧妓局,春花似錦总放、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至抵拘,卻和暖如春哎榴,著一層夾襖步出監(jiān)牢的瞬間型豁,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評(píng)論 1 274
  • 我被黑心中介騙來泰國(guó)打工尚蝌, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留迎变,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,091評(píng)論 3 378
  • 正文 我出身青樓飘言,卻偏偏與公主長(zhǎng)得像衣形,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子姿鸿,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,685評(píng)論 2 360

推薦閱讀更多精彩內(nèi)容