jvm加载class原理
轉載地址 : http://hxraid.iteye.com/blog/747625
?當Java編譯器編譯好.class文件之后,我們需要使用JVM來運行這個class文件。那么最開始的工作就是要把字節碼從磁盤輸入到內存中,這個過程我們叫做【加載 】。加載完成之后,我們就可以進行一系列的運行前準備工作了,比如: 為類靜態變量開辟空間,將常量池存放在方法區內存中并實現常量池地址解析,初始化類靜態變量等等。這篇文章我們要好好談談JVM是如何加載class文件的?
?
1、JVM加載類的過程
????? 當我們使用命令來執行某一個Java程序(比如Test.class)的時候:java Test
????? (1) java.exe 會幫助我們找到 JRE ,接著找到位于 JRE 內部的 jvm.dll ,這才是真正的 Java 虛擬機器 , 最后加載動態庫,激活 Java 虛擬機器。
????? (2) 虛擬機器激活以后,會先做一些初始化的動作,比如說讀取系統參數等。一旦初始化動作完成之后,就會產生第一個類裝載器 ―― Bootstrap Loader(啟動類裝載器 ) 。
????? (3) Bootstrap Loader 所做的初始工作中,除了一些基本的初始化動作之外,最重要的就是加載 Launcher.java 之中的 ExtClassLoader(擴展類裝載器) ,并設定其 Parent 為 null ,代表其父加載器為 BootstrapLoader 。
????? (4) 然后 Bootstrap Loader 再要求加載 Launcher.java 之中的 AppClassLoader(用戶自定義類裝載器 ) ,并設定其 Parent 為之前產生的 ExtClassLoader 實體。這兩個加載器都是以靜態類的形式存在的。
????? 這里要請大家注意的是, Launcher$ExtClassLoader.class 與 Launcher$AppClassLoader.class 都是由 Bootstrap Loader 所加載,所以 Parent 和由哪個類加載器加載沒有關系。
????? 初學者對這個過程很難理解,我們將在下面詳細的講講類裝載器和"Parent"是什么。
?
?
2、類裝載器體系結構
?
????? JVM加載class文件必須通過一個叫做類裝載器的程序,它的作用就是從磁盤文件中將要運行代碼的字節碼流加載進內存(JVM管理的方法區)中。下面是幾個比較重要的概念:
?
????? (1) 啟動類裝載器 : 每個Java虛擬機實現都必須有一個啟動類裝載器。它只負責在系統類(核心Java API的class文件)的安裝路徑中查找要裝入的類。這個裝載器的實現由C++ 所撰寫而成,是JVM實現的一部分。
????? (2) 擴展類裝載器和自定義類裝載器 : 負責除核心Java API以外的其它class文件的裝載。例如、用于安裝或下載標準擴展的class文件,在類路徑中發現的類庫的class文件,用于應用程序運行的class文件等等。這里有一點需要注意:自定義類裝載器并非由應用程序員自己實現,它也是JVM
?
????? (3) 命名空間:? Java虛擬機為每一個類裝載器維護一個唯一標識的命名空間。一個Java程序可以多次裝載具有同一個全限定名的多個類。 Java虛擬機要確定這"多個類"的唯一性,因此,當多個類 裝載器都裝載了同名的類時,為了唯一地標識這個類,還要在類名前加上裝載該類的類裝載器的標識(指出了類所位于的命名空間)。下圖顯示了兩個類裝載器有關的命名空間,顯然,不同的類裝載器允許裝載相同的類Volcano。
????? 命名空間有助于安全的實現,因為你可以有效地在裝入了不同命名空間的類之間設置一個防護罩。在Java虛擬機中,在同一個命名空間內的類可以直接進行交 互,而不同的命名空間中的類甚至不能察覺彼此的存在,除非顯式地提供了允許它們進行交互的機制。一旦加載后,如果一個惡意的類被賦予權限訪問其他虛擬機加 載的當前類,它就可以潛在地知道一些它不應該知道的信息,或者干擾程序的正常運行。
?
?
3、雙親委托模型
?
????? 用戶自定義類裝載器經常依賴其他類裝載器——至少依賴于虛擬機啟動時創建的啟動類裝載器—來幫助它實現一些類裝載請求:.在版本1.2前,非啟動類裝載器 必須顯式地求助于其他類裝載器,類裝載器可以請求另一個用戶自定義的類裝載器來裝載一個類,這個請求是通過對被請求的用戶自定義類裝載器調用 loadClass()來實現的。除此以外,類裝載器也可以通過調用findSystemClass()來請求啟動類裝載器來裝載類,這是類 ClassLoader中的一個靜態方法。
????? 在版本1.2中,類裝載器請求另一個類裝載器來裝載類型的過程被形式化,稱為雙親委派模式 。
????? 從版本1.2開始、除啟動類裝載器以外的每一個類裝載器,都有一個“雙親”類裝載器 ,在某個特定的類裝載器試圖以常用方式裝載類型以前,它會先默認地將這個任務“委派”給它的雙親——清求它的雙親來裝載這個類型。這個雙親再依次請求它自 己的雙親來裝載這個類型。這個委派的過程一直向上繼續,直到達到啟動類裝載器,通常啟動類裝載器是委派鏈中的最后一個類裝載器。如果一個類裝載器的雙親類 裝載器有能力來裝載這個類型。則這個類裝載器返回這個類型。否則,這個類裝載器試圖自己來裝載這個類。
?
???? 當Java虛擬機開始運行時,在應用程序開始啟動以前,它至少創建一個用戶自定義裝載器,也可能創建多個.所有這些裝載器被連接在一個Parent-Child的委托鏈中,在這個鏈的頂端是啟動類裝載器。
?
??? 例如:假設你寫了一個應用程序,在虛擬機上運行它.虛擬機在啟動時實例化了兩個用戶自定義類裝載器:一個"擴展類裝載器",一個"類路徑類裝載器".這些類裝載器和啟動類裝載器一起聯入一個Parent-Child委托鏈中,如下圖所示.
?
?????? 上圖所示類路徑類裝載器的Parent是擴展類裝載器, 擴展類裝載器的Parent是啟動類裝載器.在圖2中,類路徑類裝載器就被實例為系統類裝載器.假設你的程序實例化它的網絡類裝載器,它就指明了系統類裝載器作為它的Parent.
下面的例程說明了類裝載器的父子關系.
?
以上例程的輸出為:
ClassLoader name = java.net.URLClassLoader
Parent ClassLoader name = sun.misc.Launcher$AppClassLoader
Parent Parent ClassLoader name = sun.misc.Launcher$ExtClassLoader
Parent Parent Parent ClassLoader name = BootstrapClassLoader
?
?
類裝載器請求過程
以上例程1為例.將main方法改為:
??????? ClassLoaderTest tc = new ClassLoaderTest();
??????? ClassLoaderTest.testClassLoader(tc);
輸出為:
ClassLoader name = sun.misc.Launcher$AppClassLoader
Parent ClassLoader name = sun.misc.Launcher$ExtClassLoader
Parent Parent ClassLoader name = BootstrapClassLoader
?
???? 程序運行過程中,類路徑類裝載器發出一個裝載ClassLoaderTest類的請求, 類路徑類裝載器必須首先詢問它的Parent---擴展類裝載器 ---來查找并裝載這個類,同樣擴展類裝載器首先詢問啟動類裝載器。由于ClassLoaderTest不是 Java API(JAVA_HOME\jre\lib)中的類,也不在已安裝擴展路徑(JAVA_HOME\jre\lib\ext)上,這兩類裝載器 都將返回而不會提供一個名為ClassLoaderTest的已裝載類給類路徑類裝載器。類路徑類裝載器只能以它自己的方式來裝載 ClassLoaderTest,它會從當前類路徑上下載這個類。這樣,ClassLoaderTest就可以在應用程序后面的執行中發揮作用。
????? 在上例中,ClassLoaderTest類的testClassLoader方法被首次調用,該方法引用了Java API中的類 java.lang.String。Java虛擬機會請求裝載ClassLoaderTest類的類路徑類裝載器來裝載 java.lang.String。就像前面一樣,類路徑類裝載器首先將請求傳遞給它的Parent類裝載器,然后這個請求一路被委托到啟動類裝載器。但 是,啟動類裝載器可以將java.lang.String類返回給類路徑類裝載器,因為它可以找到這個類,這樣擴展類裝載器就不必在已安裝擴展路徑中查找 這個類,類路徑類裝載器也不必在類路徑中查找這個類。擴展類裝載器和類路徑類裝載器僅需要返回由啟動類裝載器返回的類java.lang.String。 從這一刻開始,不管何時ClassLoaderTest類引用了名為java.lang.String的類,虛擬機就可以直接使用這個 java.lang.String類了。
4、一個經典的實例說明
?
我們看看下面的代碼:
Java代碼 ??????? 大家發現什么不同了嗎?對了,我們寫了一個與JDK中String一模一樣的類,連包java.lang都一樣,唯一不同的是我們自定義的String類有一個main函數。我們來運行一下:
???????????????????? java.lang.NoSuchMethodError: main
???????????????????? Exception in thread "main"
這是為什么? 我們的String類不是明明有main方法嗎?
?
其實聯系我們上面講到的雙親委托模型,我們就能解釋這個問題了。
????? 運行這段代碼,JVM會首先創建一個自定義類加載器,不妨叫做AppClassLoader,并把這個加載器鏈接到委托鏈中:AppClassLoader -> ExtClassLoader -> BootstrapLoader。
????? 然后AppClassLoader會將加載java.lang.String的請求委托給ExtClassLoader,而 ExtClassLoader又會委托給最后的啟動類加載器BootstrapLoader。
????? 啟動類加載器BootstrapLoader只能加載JAVA_HOME\jre\lib中的class類(即J2SE API),問題是標準API中確實有一個java.lang.String(注意,這個類和我們自定義的類是完全兩個類)。BootstrapLoader以為找到了這個類,毫不猶豫的加載了j2se api中的java.lang.String。
????? 最后出現上面的加載錯誤(注意不是異常,是錯誤,JVM退出),因為API中的String類是沒有main方法的。
?
結論:我們當然可以自定義一個和API完全一樣的類,但是由于雙親委托模型,使得我們不可能加載上我們自定義的這樣一個類。所以J2SE規范中希望我們自定義的包有自己唯一的特色(網絡域名)。還有一點,這種加載器原理使得JVM更加安全的運行程序,因為黑客很難隨意的替代掉API中的代碼了。
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎
總結
以上是生活随笔為你收集整理的jvm加载class原理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JMS学习(4):--Spring和Ac
- 下一篇: 希尔排序理解