Java classloader和namespace詳細介紹
Java classloader和namespace詳細介紹
Java虛擬機通過裝載、連接和初始化一個JAVA類型,使該類型可以被正在運行的JAVA程序所使用。其中,裝載就是把二進制形式的JAVA類型讀入JAVA虛擬機中。連接就是把這種已經(jīng)讀入虛擬機的二進制形式的類型數(shù)據(jù)合并到虛擬機的運行時狀態(tài)中去。連接階段分為三個步驟-驗證、準備和解析。驗證確保了JAVA類型數(shù)據(jù)格式正確并適于JAVA虛擬機使用。準備負責為該類分配它所需的內存,比如為它的類變量分配內存。解析把常量池中的符號引用轉換為直接引用,如內存地址指針。在初始化期間,激活類的靜態(tài)變量的初始化代碼和靜態(tài)代碼塊。
裝載步驟的最終產品是一個被裝載類型的Class類的實例對象,它成為JAVA程序與內部數(shù)據(jù)結構之間的接口。對于每一個被裝載的類型,虛擬機都會相應地為它創(chuàng)建一個Class類的實例。
1 類裝載器的安全作用
JAVA類裝載器在JAVA安全體系結構中起著最關重要的作用,是JAVA安全沙箱的第一道防線。類裝載器體系結構在三個方面對JAVA的沙箱起作用:
1) 它防止惡意代碼去干涉善意的代碼
2) 它守護了被信任的類庫的邊界
3) 它將代碼歸入某類(稱為保護域),該類確定了代碼可以進行哪些操作。
類裝載器體系結構可以防止惡意代碼去干涉善意的代碼,這是通過為不同的類裝載器裝入的類提供不同的命名空間來實現(xiàn)的。
2雙親委派模型
JAVA虛擬機規(guī)范定義了兩種類型的類裝載器-啟動類裝載器和用戶自定義類裝載器,啟動類裝載器是JAVA虛擬機實現(xiàn)的一部分,通過繼承ClassLoader類,用戶可以創(chuàng)建自定義的類裝載器來完成特定要求的加載。JAVA虛擬機已經(jīng)創(chuàng)建了2個自定義類裝載器-擴展類裝載器和系統(tǒng)類裝載器。
每一個用戶自定義的類裝載器在創(chuàng)建時被分配一個“雙親”parent類裝載器。如果沒有顯示地傳遞一個雙親類裝載器給用戶自定義的類裝載器的構造方法,系統(tǒng)類裝載器就默認被指定為雙親。如果傳遞到構造方法的是一個已有的用戶自定義類裝載器的引用,該用戶自定義類裝載器就作為雙親;如果向構造方法傳遞了null,啟動類裝載器就是雙親。
啟動類裝載器Bootstrap Classloader:它是JAVA虛擬機實現(xiàn)的一部分,是c/c++實現(xiàn)的,它沒有雙親。啟動類裝載器裝載JAVA核心庫代碼。
擴展類裝載器Extension Classloader:繼承自URLClassLoader,初始化向構造方法傳遞了null,所以雙親是Bootstrap Classloaser。它從java.ext.dirs擴展目錄中裝載代碼。
系統(tǒng)類裝載器Application Classloader:繼承自URLClassLoader,雙親是Extension Classloaser。它從CLASSPATH路徑中裝載應用程序代碼。
其中,網(wǎng)絡類裝載器URLClassLoader是JAVA庫提供的一個類裝載器,用來從網(wǎng)絡其他位置裝載類。
雙親孩子類裝載器委派鏈
在雙親委派模型下,當一個裝載器被請求裝載某個類時,它首先委托自己的雙親parent去裝載,若parent能裝載,則返回這個類所對應的Class對象,若parent不能裝載,則由parent的請求者去裝載。
現(xiàn)在假設要求Cindy去裝載一個名為java.io.FileReader的類型。Cindy第一件事情就是去找Mom來裝載那個類型;Mom所做的第一件事情就是去找Grandma來裝載那個類型;而Grandma首先去找啟動類裝載器去裝載。在這個例子中,啟動類裝載器可以裝載那個類型,它就返回代表java.io.FileReader的Class實例給Grandma。Grandma傳遞該Class的引用 Mom,Mom再回傳給Cindy,Cindy返回給程序。
在此模型下,啟動類裝載器可以搶在擴展類裝載器之前去裝載類,而擴展類裝載器可以搶在系統(tǒng)類裝載器之前去裝載那個類,系統(tǒng)類裝載器又可以搶在網(wǎng)絡類裝載器之前去裝載它。這樣,使用雙親-孩子委派鏈的方式,啟動類裝載器會在最可信的類庫-核心Java API-中首先檢查每個被裝載的類型,然后,才依次到擴展路徑、系統(tǒng)類路徑中檢查被裝載的類型文件。用這種方法,類裝載器的體系結構就可以防止不可靠的代碼用它們自己的版本來替代可以信任的類。
3命名空間
由不同的類裝載器裝載的類將被放在虛擬機內部的不同命名空間。命名空間由一系列唯一的名稱組成,每一個被裝載的類有一個名字。JAVA虛擬機為每一個類裝載器維護一個名字空間。例如,一旦JAVA虛擬機將一個名為Volcano的類裝入一個特定的命名空間,它就不能再裝載名為Valcano的其他類到相同的命名空間了??梢园讯鄠€Valcano類裝入一個JAVA虛擬機中,因為可以通過創(chuàng)建多個類裝載器從而在一個JAVA應用程序中創(chuàng)建多個命名空間。
1) 初始類裝載器/ 定義類裝載器
命名空間有助于安全的實現(xiàn),因為你可以有效地在裝入了不同命名空間的類之間設置一個防護罩。在JAVA虛擬機中,在同一個命名空間內的類可以直接進行交互,而不同的命名空間中的類甚至不能覺察彼此的存在,除非顯示地提供了允許它們進行交互的機制,如獲取Class對象的引用后使用反射來訪問。
如果要求某個類裝載器去裝載一個類型,但是卻返回了其他類裝載器裝載的類型,這種裝載器被稱為是那個類型的初始類裝載器 ;而實際裝載那個類型的類裝載器被稱為該類型的定義類裝載器 。任何被要求裝載類型,并且能夠返回Class實例的引用代表這個類型的類裝載器,都是這個類型的初始類裝載器。在上面的一個例子中,java.io.FileReader定義類裝載器是啟動類裝載器,Cindy、Mom、Grandma、啟動類裝載器都是初始類裝載器。
虛擬機會為每一個類裝載器維護一張列表,列表中是已經(jīng)被請求過的類型的名字。這些列表包含了每一個類裝載器被標記為初始類裝載器的類型,它們代表了每一個類裝載器的命名空間。虛擬機總是會在調用loadClass()之前檢查這個內部列表,如果這個類裝載器已經(jīng)被標記為是這個具有該全限定名的類型的初始類裝載器,就會返回表示這個類型的Class實例,這樣,虛擬機永遠不會自動在同一個用戶自定義類裝載器上調用同一個名字的類型兩次。
2) 命名空間的類型共享
前面提到過只有同一個命名空間內的類才可以直接進行交互,但是我們經(jīng)常在由用戶自定義類裝載器定義的類型中直接使用Java API類,這不是矛盾了嗎?這是類型共享 原因-如果某個類裝載器把類型裝載的任務委派給另外一個類裝載器,而后者定義了這個類型,那么被委派的類裝載器裝載的這個類型,在所有被標記為該類型的初始類裝載器的命名空間中共享。
例如上面的例子中,Cindy可以共享Mon、Grandma、啟動類裝載器的命名空間中的類型,Kenny也可以共享 Mon、Grandma、啟動類裝載器的 命名空間中的 類型,但是Cindy和Kenny的命名空間不能共享。
3) 運行時包
每個類裝載器都有自己的命名空間,其中維護著由它裝載的類型。所以一個JAVA程序可以多次裝載具有同一個全限定名的多個類型。這樣一個類型的全限定名就不足以確定在一個JAVA虛擬機中的唯一性。因此,當多個類裝載器都裝載了同名的類型時,為了唯一表示該類型,還要在類型名稱前加上裝載該類型的類裝載器來表示-[classloader class]。
在允許兩個類型之間對包內可見的成員進行訪問前,虛擬機不但要確定這個兩個類型屬于同一個包,還必須確認它們屬于同一個運行時包-它們必須有同一個類裝載器裝載的。這樣,java.lang.Virus和來自核心的java.lang的類不屬于同一個運行時包,java.lang.Virus就不能訪問JAVA API的java.lang包中的包內可見的成員。
4自定義類裝載器
JAVA類型要么由啟動類裝載器裝載,要么通過用戶自定義的類裝載器裝載。啟動類裝載器是虛擬機實現(xiàn)的一部分,它以與實現(xiàn)無關的方式裝載類型,JAVA提供了抽象類java.lang.ClassLoader,用戶自定義的類裝載器是類ClassLoader的子類實例,它以定制的方式裝載類。所有用戶自定義類裝載器都實例化自ClassLoader的子類。
下面提供一個簡單的用戶自定義類裝載器。
import java.io.*; public class UserDefinedClassLoader extends ClassLoader { private String directory = "d:/classes/"; private String extensionType = ".class"; public UserDefinedClassLoader() { super(); // this set the parent as the AppClassLoader by default } public UserDefinedClassLoader( ClassLoader parent ) { super( parent ); } public Class findClass( String name ) { byte[] data = loadClassData( name ); return defineClass( name, data, 0, data.length ); } private byte[] loadClassData( String name ) { byte[] data = null; try { FileInputStream in = new FileInputStream( new File( directory + name.replace( '.', '/') + extensionType ) ); ByteArrayOutputStream out = new ByteArrayOutputStream(); int ch = 0; while( ( ch = in.read() ) != -1 ) { out.write( ch ); } data = out.toByteArray(); } catch ( IOException e ) { e.printStackTrace(); } return data; } }
public class Valcano { static { System.out.println("Valcano Class Initialized"); } public Valcano() { } } public class ClassLoaderTest { public static void main( String[] args ) { try { UserDefinedClassLoader userLoader = new UserDefinedClassLoader(); Class valcanoClass1 = userLoader.loadClass( "Valcano" ); URL url = new URL("file:/d:/classes/" ); ClassLoader urlLoader = new URLClassLoader( new URL[] { url } ); Class valcanoClass2 = urlLoader.loadClass( "Valcano" ); System.out.println( "valcanoClass1 classloaer = " + valcanoClass1.getClassLoader() ); System.out.println( "valcanoClass2 classloaer = " + valcanoClass2.getClassLoader() ); System.out.println( "valcanoClass1 = valcanoClass2 ? " + ( valcanoClass1 == valcanoClass2 ) ); } catch( Exception e ) { e.printStackTrace(); } } }
輸出結果:
valcanoClass1 classloaer = UserDefinedClassLoader@1fb8ee3 valcanoClass2 classloaer = java.NET.URLClassLoader@14318bb valcanoClass1 = valcanoClass2 ? false
我們可以看到,有兩個不同的Valcano的Class實例被加載到同一個虛擬機中。
另外我們看到Valcano類靜態(tài)初始化語句沒有被執(zhí)行,意味著類沒有被初始化,這是因為JAVA中只有當類被主動使用時類型才會進行初始化。
感謝閱讀,希望能幫助到大家,謝謝大家對本站的支持!
相關文章
springboot如何解決跨域后session獲取不到sessionId不一致
這篇文章主要介紹了springboot如何解決跨域后session獲取不到sessionId不一致問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-01-01SpringBoot2如何集成Elasticsearch6.x(TransportClient方式)
這篇文章主要介紹了SpringBoot2如何集成Elasticsearch6.x(TransportClient方式)問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-05-05SpringBoot編譯target目錄下沒有resource下的文件踩坑記錄
這篇文章主要介紹了SpringBoot編譯target目錄下沒有resource下的文件踩坑記錄,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-08-08java中ConcurrentHashMap的讀操作為什么不需要加鎖
ConcurrentHashMap完全允許多個讀操作并發(fā)進行,讀操作并不需要加鎖。所以下面這篇文章主要給大家介紹了關于java中ConcurrentHashMap的讀操作為什么不需要加鎖的相關資料,需要的朋友可以參考下2018-10-10Java多線程并發(fā)編程(互斥鎖Reentrant Lock)
這篇文章主要介紹了ReentrantLock 互斥鎖,在同一時間只能被一個線程所占有,在被持有后并未釋放之前,其他線程若想獲得該鎖只能等待或放棄,需要的朋友可以參考下2017-05-05