JVM類加載深度剖析

1邢隧、類加載過程

查看是否加載命令 VM options:-verbose:class

多個(gè)java文件經(jīng)過編譯打包生成可運(yùn)行jar包,最終由java命令運(yùn)行某個(gè)主類的main函數(shù)啟
動(dòng)程序,這里首先需要通過類加載器把主類加載到JVM。 主類在運(yùn)行過程中如果使用到其它類,會逐步加載這些類票从。 注意,jar包里的類不是一次性全部加載的滨嘱,是使用到時(shí)才加載峰鄙。

類加載到使用整個(gè)過程有如下幾步
加載 >> 驗(yàn)證 >> 準(zhǔn)備 >> 解析 >> 初始化 >> 使用 >> 卸載

  • 加載:在硬盤上查找并通過IO讀入字節(jié)碼文件,使用到類時(shí)才會加載太雨,例如調(diào)用 類的main()方法吟榴,new對象等等
  • 驗(yàn)證:校驗(yàn)字節(jié)碼文件的正確性
    -類的靜態(tài)變量分配內(nèi)存,并賦予默認(rèn)值
  • 解析:將符號引用替換為直接引用囊扳,該階段會把一些靜態(tài)方法(符號引用吩翻,比如
    main()方法)替換為指向數(shù)據(jù)所存內(nèi)存的指針或句柄等(直接引用),這是所謂的靜態(tài)鏈 接過程(類加載期間完成)宪拥,動(dòng)態(tài)鏈接是在程序運(yùn)行期間完成的將符號引用替換為直接 引用仿野,下節(jié)課會講到動(dòng)態(tài)鏈接
  • 對類的靜態(tài)變量初始化為指定的值,執(zhí)行靜態(tài)代碼塊


    image.png

2她君、類加載器和雙親委派機(jī)制

上面的類加載過程主要是通過類加載器來實(shí)現(xiàn)的,Java里有如下幾種類加載器

  • 啟動(dòng)類加載器:負(fù)責(zé)加載支撐JVM運(yùn)行的位于JRE的lib目錄下的核心類庫葫哗,比如 rt.jar缔刹、charsets.jar等
  • 擴(kuò)展類加載器:負(fù)責(zé)加載支撐JVM運(yùn)行的位于JRE的lib目錄下的ext擴(kuò)展目錄中 的JAR類包
  • 應(yīng)用程序類加載器:負(fù)責(zé)加載ClassPath路徑下的類包,主要就是加載你自己寫 的那些類
  • 自定義加載器:負(fù)責(zé)加載用戶自定義路徑下的類包

看一個(gè)類加載器示例:

1 publicclassTestJDKClassLoader{
2 public static void main(String[] args){
3 System.out.println(String.class.getClassLoader());
4 System.out.println(com.sun.crypto.provider.DESKeyFactory.class.getClassL oader().getClass().getName());
5
System.out.println(TestJDKClassLoader.class.getClassLoader().getClass().get Name());
6
System.out.println(ClassLoader.getSystemClassLoader().getClass().getName()) 7}
8}
9
10 運(yùn)行結(jié)果:
11 null//啟動(dòng)類加載器是C++語言實(shí)現(xiàn)劣针,所以打印不出來 12 sun.misc.Launcher$ExtClassLoader
13 sun.misc.Launcher$AppClassLoader
14 sun.misc.Launcher$AppClassLoader

自定義一個(gè)類加載器示例:
自定義類加載器只需要繼承 java.lang.ClassLoader 類校镐,該類有兩個(gè)核心方法,一個(gè)是 loadClass(String, boolean)捺典,實(shí)現(xiàn)了雙親委派機(jī)制鸟廓,大體邏輯

  1. 首先,檢查一下指定名稱的類是否已經(jīng)加載過襟己,如果加載過了引谜,就不需要再加載, 直接返回擎浴。
  2. 如果此類沒有加載過员咽,那么,再判斷一下是否有父加載器;如果有父加載器贮预,則由 父加載器加載(即調(diào)用parent.loadClass(name, false);).或者是調(diào)用bootstrap類加 載器來加載贝室。
  3. 如果父加載器及bootstrap類加載器都沒有找到指定的類契讲,那么調(diào)用當(dāng)前類加載器 的findClass方法來完成類加載。
    還有一個(gè)方法是findClass滑频,默認(rèn)實(shí)現(xiàn)是拋出異常捡偏,所以我們自定義類加載器主要是重寫findClass方法。

2
3
4
5
6 7} 8
9
10
11
12
13
14
15
16
17
18
19
20
{
21
22
private byte[] loadByte(String name) throws Exception {
name = name.replaceAll("\\.", "/");
FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class");
int len = fis.available(); byte[] data = new byte[len]; fis.read(data); fis.close();
 return data;
 }
 protected Class<?> findClass(String name) throws ClassNotFoundException
try{
byte[] data = loadByte(name);
publicclassMyClassLoaderTest{
static class MyClassLoader extends ClassLoader { private String classPath;
public MyClassLoader(String classPath) { this.classPath = classPath;
//defineClass將一個(gè)字節(jié)數(shù)組轉(zhuǎn)為Class對象峡迷,這個(gè)字節(jié)數(shù)組是class文件讀取后最終 的字節(jié)數(shù)組银伟。
24 return defineClass(name, data, 0, data.length);
25 } catch (Exception e) {
26 e.printStackTrace();
27 throw new ClassNotFoundException();
28 }
29 }
30
31 } 32
33 public static void main(String args[]) throws Exception {
34 MyClassLoader classLoader = new MyClassLoader("D:/test");
35 Class clazz = classLoader.loadClass("com.tuling.jvm.User1");
36 Object obj = clazz.newInstance();
37 Method method= clazz.getDeclaredMethod("sout", null);

method.invoke(obj, null); System.out.println(clazz.getClassLoader().getClass().getName()); }
}
運(yùn)行結(jié)果: =======自己的加載器加載類調(diào)用方法======= com.tuling.jvm.MyClassLoaderTest$MyClassLoader

雙親委派機(jī)制 JVM類加載器是有親子層級結(jié)構(gòu)的,如下圖


image.png

這里類加載其實(shí)就有一個(gè)雙親委派機(jī)制凉当,加載某個(gè)類時(shí)會先委托父加載器尋找目標(biāo)類枣申,找不 到再委托上層父加載器加載,如果所有父加載器在自己的加載類路徑下都找不到目標(biāo)類看杭,則 在自己的類加載路徑中查找并載入目標(biāo)類忠藤。 比如我們的Math類,最先會找應(yīng)用程序類加載器加載楼雹,應(yīng)用程序類加載器會先委托擴(kuò)展類 加載器加載模孩,擴(kuò)展類加載器再委托啟動(dòng)類加載器,頂層啟動(dòng)類加載器在自己的類加載路徑里 找了半天沒找到Math類贮缅,則向下退回加載Math類的請求榨咐,擴(kuò)展類加載器收到回復(fù)就自己加 載,在自己的類加載路徑里找了半天也沒找到Math類谴供,又向下退回Math類的加載請求給應(yīng) 用程序類加載器块茁,應(yīng)用程序類加載器于是在自己的類加載路徑里找Math類,結(jié)果找到了就 自己加載了桂肌。数焊。

雙親委派機(jī)制說簡單點(diǎn)就是,先找父親加載崎场,不行再由兒子自己加載

  • 沙箱安全機(jī)制:自己寫的java.lang.String.class類不會被加載佩耳,這樣便可以防止
    核心API庫被隨意篡改
  • 避免類的重復(fù)加載:當(dāng)父親已經(jīng)加載了該類時(shí),就沒有必要子ClassLoader再加
    載一次谭跨,保證被加載類的唯一性

看一個(gè)類加載示例:

packagejava.lang;
publicclassString{
public static void main(String[] args) { System.out.println("**************My String Class**************");
9 運(yùn)行結(jié)果:
10 11 12
錯(cuò)誤: 在類 java.lang.String 中找不到 main 方法, 請將 main 方法定義為: public static void main(String[] args)
否則 JavaFX 應(yīng)用程序類必須擴(kuò)展javafx.application.Application

再來一個(gè)沙箱安全機(jī)制示例干厚,嘗試打破雙親委派機(jī)制,用自定義類加載器加載我們自己實(shí)現(xiàn) 的 java.lang.String.class

publicclassMyClassLoaderTest{
static class MyClassLoader extends ClassLoader { 
private String classPath;
public MyClassLoader(String classPath) { this.classPath = classPath;
 private byte[] loadByte(String name) throws Exception {
 name = name.replaceAll("\\.", "/");
 FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class");
int len = fis.available();
byte[] data = new byte[len];
 fis.read(data);
fis.close();
 return data;
 }
20
21 protected Class<?> findClass(String name) throws ClassNotFoundException {
22 try{
23 byte[] data = loadByte(name);
24 return defineClass(name, data, 0, data.length);
25 } catch (Exception e) {
26 e.printStackTrace();
27 throw new ClassNotFoundException();
28 }
29 }
30
31 /**
32 * 重寫類加載方法螃宙,實(shí)現(xiàn)自己的加載邏輯蛮瞄,不委派給雙親加載
33 * @param name
34 * @param resolve
35 * @return
36 * @throws ClassNotFoundException
37 */
38 protected Class<?> loadClass(String name, boolean resolve)
39 throws ClassNotFoundException {
40 synchronized (getClassLoadingLock(name)) {
41 // First, check if the class has already been loaded
42 Class<?> c = findLoadedClass(name);
43
44 if(c==null){
45 // If still not found, then invoke findClass in order
46 // to find the class.
47 long t1 = System.nanoTime();
48 c = findClass(name);
49
50 // this is the defining class loader; record the stats
51 sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
52 sun.misc.PerfCounter.getFindClasses().increment();
53 }
54 if (resolve) {
55 resolveClass(c);
56 }
57 return c;
}
59 }
60 }
61
62 public static void main(String args[]) throws Exception {
63 MyClassLoader classLoader = new MyClassLoader("D:/test");
64 //嘗試用自己改寫類加載機(jī)制去加載自己寫的java.lang.String.class
65 Class clazz = classLoader.loadClass("java.lang.String");
66 Object obj = clazz.newInstance();
67 Method method= clazz.getDeclaredMethod("sout", null);
68 method.invoke(obj, null);
69 System.out.println(clazz.getClassLoader().getClass().getName());
70 }
71 }
72
73 運(yùn)行結(jié)果:
74 java.lang.SecurityException:Prohibitedpackagename:java.lang
75 at java.lang.ClassLoader.preDefineClass(ClassLoader.java:659)
76 at java.lang.ClassLoader.defineClass(ClassLoader.java:758)

打破雙親委派
以Tomcat類加載為例,Tomcat 如果使用默認(rèn)的雙親委派類加載機(jī)制行不行? 我們思考一下:Tomcat是個(gè)web容器污呼, 那么它要解決什么問題:

  1. 一個(gè)web容器可能需要部署兩個(gè)應(yīng)用程序裕坊,不同的應(yīng)用程序可能會依賴同一個(gè)第三方類 庫的不同版本,不能要求同一個(gè)類庫在同一個(gè)服務(wù)器只有一份燕酷,因此要保證每個(gè)應(yīng)用程序的 類庫都是獨(dú)立的籍凝,保證相互隔離周瞎。
  2. 部署在同一個(gè)web容器中相同的類庫相同的版本可以共享。否則饵蒂,如果服務(wù)器有10個(gè)應(yīng) 用程序声诸,那么要有10份相同的類庫加載進(jìn)虛擬機(jī)。
  3. web容器也有自己依賴的類庫退盯,不能與應(yīng)用程序的類庫混淆彼乌。基于安全考慮渊迁,應(yīng)該讓容 器的類庫和程序的類庫隔離開來慰照。
  4. web容器要支持jsp的修改,我們知道琉朽,jsp 文件最終也是要編譯成class文件才能在虛擬 機(jī)中運(yùn)行毒租,但程序運(yùn)行后修改jsp已經(jīng)是司空見慣的事情, web容器需要支持 jsp 修改后不 用重啟箱叁。

再看看我們的問題:Tomcat 如果使用默認(rèn)的雙親委派類加載機(jī)制行不行? 答案是不行的墅垮。為什么?
第一個(gè)問題,如果使用默認(rèn)的類加載器機(jī)制耕漱,那么是無法加載兩個(gè)相同類庫的不同版本的算色, 默認(rèn)的類加器是不管你是什么版本的,只在乎你的全限定類名螟够,并且只有一份灾梦。第二個(gè)問 題,默認(rèn)的類加載器是能夠?qū)崿F(xiàn)的妓笙,因?yàn)樗穆氊?zé)就是保證唯一性斥废。 第三個(gè)問題和第一個(gè)問題一樣。 我們再看第四個(gè)問題给郊,我們想我們要怎么實(shí)現(xiàn)jsp文件的熱加載,jsp 文件其實(shí)也就是class 文件捧灰,那么如果修改了淆九,但類名還是一樣,類加載器會直接取方法區(qū)中已經(jīng)存在的毛俏,修改后 的jsp是不會重新加載的炭庙。那么怎么辦呢?我們可以直接卸載掉這jsp文件的類加載器,所以 你應(yīng)該想到了煌寇,每個(gè)jsp文件對應(yīng)一個(gè)唯一的類加載器焕蹄,當(dāng)一個(gè)jsp文件修改了,就直接卸載 這個(gè)jsp類加載器阀溶。重新創(chuàng)建類加載器腻脏,重新加載jsp文件鸦泳。

Tomcat自定義加載器詳解


image.png

tomcat的幾個(gè)主要類加載器:

  • commonLoader:Tomcat最基本的類加載器,加載路徑中的class可以被
    Tomcat容器本身以及各個(gè)Webapp訪問;
  • catalinaLoader:Tomcat容器私有的類加載器永品,加載路徑中的class對于
    Webapp不可見;
  • sharedLoader:各個(gè)Webapp共享的類加載器做鹰,加載路徑中的class對于所有
    Webapp可見,但是對于Tomcat容器不可見;
  • WebappClassLoader:各個(gè)Webapp私有的類加載器鼎姐,加載路徑中的class只對
    當(dāng)前Webapp可見

從圖中的委派關(guān)系中可以看出: CommonClassLoader能加載的類都可以被CatalinaClassLoader和SharedClassLoader使 用钾麸,從而實(shí)現(xiàn)了公有類庫的共用,而CatalinaClassLoader和SharedClassLoader自己能加 載的類則與對方相互隔離炕桨。 WebAppClassLoader可以使用SharedClassLoader加載到的類饭尝,但各個(gè) WebAppClassLoader實(shí)例之間相互隔離。 而JasperLoader的加載范圍僅僅是這個(gè)JSP文件所編譯出來的那一個(gè).Class文件献宫,它出現(xiàn)的 目的就是為了被丟棄:當(dāng)Web容器檢測到JSP文件被修改時(shí)钥平,會替換掉目前的 JasperLoader的實(shí)例,并通過再建立一個(gè)新的Jsp類加載器來實(shí)現(xiàn)JSP文件的熱加載功能遵蚜。
tomcat 這種類加載機(jī)制違背了java 推薦的雙親委派模型了嗎?答案是:違背了帖池。 我們前面說過,雙親委派機(jī)制要求除了頂層的啟動(dòng)類加載器之外吭净,其余的類加載器都應(yīng)當(dāng)由 自己的父類加載器加載睡汹。
很顯然,tomcat 不是這樣實(shí)現(xiàn)寂殉,tomcat 為了實(shí)現(xiàn)隔離性囚巴,沒有遵守這個(gè)約定

每個(gè) webappClassLoader加載自己的目錄下的class文件,不會傳遞給父類加載器友扰,打破了雙 親委派機(jī)制彤叉。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市村怪,隨后出現(xiàn)的幾起案子秽浇,更是在濱河造成了極大的恐慌,老刑警劉巖甚负,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件柬焕,死亡現(xiàn)場離奇詭異,居然都是意外死亡梭域,警方通過查閱死者的電腦和手機(jī)斑举,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來病涨,“玉大人富玷,你說我怎么就攤上這事。” “怎么了赎懦?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵雀鹃,是天一觀的道長。 經(jīng)常有香客問我铲敛,道長褐澎,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任伐蒋,我火速辦了婚禮工三,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘先鱼。我一直安慰自己俭正,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布焙畔。 她就那樣靜靜地躺著掸读,像睡著了一般。 火紅的嫁衣襯著肌膚如雪宏多。 梳的紋絲不亂的頭發(fā)上儿惫,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天,我揣著相機(jī)與錄音伸但,去河邊找鬼肾请。 笑死,一個(gè)胖子當(dāng)著我的面吹牛更胖,可吹牛的內(nèi)容都是我干的铛铁。 我是一名探鬼主播,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼却妨,長吁一口氣:“原來是場噩夢啊……” “哼饵逐!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起彪标,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤倍权,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后捞烟,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體账锹,經(jīng)...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年坷襟,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片生年。...
    茶點(diǎn)故事閱讀 40,664評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡婴程,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出抱婉,到底是詐尸還是另有隱情档叔,我是刑警寧澤桌粉,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站衙四,受9級特大地震影響铃肯,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜传蹈,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一押逼、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧惦界,春花似錦挑格、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至灾搏,卻和暖如春挫望,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背狂窑。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工媳板, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蕾域。 一個(gè)月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓拷肌,卻偏偏與公主長得像,于是被迫代替她去往敵國和親旨巷。 傳聞我的和親對象是個(gè)殘疾皇子巨缘,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,675評論 2 359