- 相關(guān)推薦
2016最新java classloader詳解
Classloader 類(lèi)加載器,用來(lái)加載Java類(lèi)到 Java 虛擬機中的一種加載器。那么Classloader 類(lèi)有什么原理呢?下面跟yjbys小編一起來(lái)學(xué)習一下!
JAVA啟動(dòng)后,是經(jīng)過(guò)JVM各級ClassLoader來(lái)加載各個(gè)類(lèi)到內存。為了更加了解加載過(guò)程,我通過(guò)分析和寫(xiě)了一個(gè)簡(jiǎn)單的ClassLoader來(lái)粗淺的分析它的原理。
JVM的ClassLoader分三層,分別為Bootstrap ClassLoader,Extension ClassLoader,System ClassLoader,他們不是類(lèi)繼承的父子關(guān)系,是邏輯上的上下級關(guān)系。
Bootstrap ClassLoader是啟動(dòng)類(lèi)加載器,它是用C++編寫(xiě)的,從%jre%/lib目錄中加載類(lèi),或者運行時(shí)用-Xbootclasspath指定目錄來(lái)加載。
Extension ClassLoader是擴展類(lèi)加載器,從%jre%/lib/ext目錄加載類(lèi),或者運行時(shí)用-Djava.ext.dirs制定目錄來(lái)加載。
System ClassLoader,系統類(lèi)加載器,它會(huì )從系統環(huán)境變量配置的classpath來(lái)查找路徑,環(huán)境變量里的.表示當前目錄,是通過(guò)運行時(shí)-classpath或-Djava.class.path指定的目錄來(lái)加載類(lèi)。
一般自定義的Class Loader可以從java.lang.ClassLoader繼承,不同classloader加載相同的類(lèi),他們在內存也不是相等的,即它們不能互相轉換,會(huì )直接拋異常。java.lang.ClassLoader的核心加載方法是loadClass方法,如:
protected 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 {
c = findBootstrapClass0(name);
}
} catch (ClassNotFoundException e) {
// If still not found, then invoke findClass in order
// to find the class.
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
通過(guò)上面加載過(guò)程,我們能知道JVM默認是雙親委托加載機制,即首先判斷緩存是否有已加載的類(lèi),如果緩存沒(méi)有,但存在父加載器,則讓父加載器加載,如果不存在父加載器,則讓Bootstrap ClassLoader去加載,如果父類(lèi)加載失敗,則調用本地的findClass方法去加載。
可以通過(guò)下面三條語(yǔ)句,輸入現在加載的各個(gè)classloader的加載路徑:
System.out.println("sun.boot.class.path:" + System.getProperty("sun.boot.class.path"));
System.out.println("java.ext.dirs:" + System.getProperty("java.ext.dirs"));
System.out.println("java.class.path:" +System.getProperty("java.class.path"));
ClassLoader cl = Thread.currentThread().getContextClassLoader();//ClassLoader.getSystemClassLoader()
System.out.println("getContextClassLoader:" +cl.toString());
System.out.println("getContextClassLoader.parent:" +cl.getParent().toString());
System.out.println("getContextClassLoader.parent2:" +cl.getParent().getParent());
輸出結果為:
sun.boot.class.path:C:\Program Files\Java\jre7\lib\resources.jar;C:\Program Files\Java\jre7\lib\rt.jar;C:\Program Files\Java\jre7\lib\sunrsasign.jar;C:\Program Files\Java\jre7\lib\jsse.jar;C:\Program Files\Java\jre7\lib\jce.jar;C:\Program Files\Java\jre7\lib\charsets.jar;C:\Program Files\Java\jre7\classes
java.ext.dirs:C:\Program Files\Java\jre7\lib\ext;C:\Windows\Sun\Java\lib\ext
java.class.path:E:\MyProjects\workspace\TestConsole\bin
getContextClassLoader:sun.misc.Launcher$AppClassLoader@19dbc3b
getContextClassLoader.parent:sun.misc.Launcher$ExtClassLoader@b103dd
getContextClassLoader.parent2:null
從上面的運行結果可以看出邏輯上的層級繼承關(guān)系。雙親委托機制的作用是防止系統jar包被本地替換,因為查找方法過(guò)程都是從最底層開(kāi)始查找。 因此,一般我們自定義的classloader都需要采用這種機制,我們只需要繼承java.lang.ClassLoader實(shí)現findclass即可,如果需要更多控制,自定義的classloader就需要重寫(xiě)loadClass方法了,比如tomcat的加載過(guò)程,這個(gè)比較復雜,可以通過(guò)其他文檔資料查看相關(guān)介紹。
各個(gè)ClassLoader加載相同的類(lèi)后,他們是不互等的,這個(gè)當涉及多個(gè)ClassLoader,并且有通過(guò)當前線(xiàn)程上線(xiàn)文獲取ClassLoader后轉換特別需要注意,可以通過(guò)線(xiàn)程的setContextClassLoader設置一個(gè)ClassLoader線(xiàn)程上下文,然后再通過(guò)Thread.currentThread().getContextClassLoader()獲取當前線(xiàn)程保存的Classloader。但是自定義的類(lèi)文件,放到Bootstrap ClassLoader加載目錄,是不會(huì )被Bootstrap ClassLoader加載的,因為作為啟動(dòng)類(lèi)加載器,它不會(huì )加載自己不熟悉的jar包的,并且類(lèi)文件必須打包成jar包放到加載器加載的根目錄,才可能被擴展類(lèi)加載器所加載。
下面我自定義一個(gè)簡(jiǎn)單的classloader:
public class TestClassLoader extends ClassLoader {
//定義文件所在目錄
private static final String DEAFAULTDIR="E:\\MyProjects\\workspace\\TestConsole\\bin\\";
public Class findClass(String name) throws ClassNotFoundException {
byte[] b = null;
try {
b = loadClassData(GetClassName(name));
} catch (Exception e) {
e.printStackTrace();
}
return defineClass(name, b, 0, b.length);
}
@Override
protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
if(name.startsWith("java.")){try {
return super.loadClass(name, false);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
byte[] b = null;
try {
b = loadClassData(GetClassName(name));
} catch (Exception e) {
e.printStackTrace();
}
return defineClass(name, b, 0, b.length);
}
private byte[] loadClassData(String filepath) throws Exception {
int n =0;
BufferedInputStream br = new BufferedInputStream(
new FileInputStream(
new File(filepath)));
ByteArrayOutputStream bos= new ByteArrayOutputStream();
while((n=br.read())!=-1){
bos.write(n);
}
br.close();
return bos.toByteArray();
}
public static String GetClassName(String name){
return DEAFAULTDIR+name.replace('.','/')+".class";
}
}
這個(gè)自定義的ClassLoader重寫(xiě)了loadclass方法,但不用默認的雙親委托,比如java.lang包下面的都無(wú)法解析,這里我簡(jiǎn)單的判斷如果是java.開(kāi)始的包則用父類(lèi)去解析,能簡(jiǎn)單的滿(mǎn)足雙親委托機制,但是其他相關(guān)非系統類(lèi)加載也沒(méi)有用父類(lèi)加載了。
測試代碼如:
TestClassLoader liuloader = new TestClassLoader();
Myrunner runner = new Myrunner();
runner.setContextClassLoader(liuloader);
runner.start();
Myrunner是我自定義繼承自Thread的線(xiàn)程,通過(guò)設置線(xiàn)程上下文的classloader后,線(xiàn)程內部測試代碼如:
ClassLoader cl1 = Thread.currentThread().getContextClassLoader();
System.out.println(cl1);
它將會(huì )輸出:
com.liu.ClassLoader.TestClassLoader@347cdb,說(shuō)明已經(jīng)為當前線(xiàn)程上下文設置了自定義的Classloader了,如果這個(gè)線(xiàn)程內部通過(guò)這個(gè)classloader加載一個(gè)類(lèi),再轉換成當前的類(lèi),如代碼:
Class c = cl1.loadClass("com.liu.ClassLoader.TestLoader2"); TestLoader2 tloader = (TestLoader2)c.newInstance();
則為拋java.lang.ClassCastException異常: com.liu.ClassLoader.TestLoader2 cannot be cast to com.liu.ClassLoader.TestLoader2。
因為cl1當前是 TestClassLoader加載的,而這個(gè)TestLoader2的類(lèi)還是默認由AppClassLoader加載,因此它們不能隱式轉換,Classloader加載相同的類(lèi),內存認為它們是沒(méi)有關(guān)系的對象。
如果把我自定義的TestClassLoader里的LoadClass方法去掉,則采用了雙親委托機制,這樣我們除了指定的類(lèi)以外,其他都會(huì )優(yōu)先用父類(lèi)來(lái)加載。這樣可以解決剛才的java.lang.ClassCastException異常問(wèn)題,為加載的對象建立一個(gè)抽象父類(lèi),自定義的Classloader負責加載子類(lèi),父類(lèi)統一交給AppClassLoader或父加載器來(lái)加載,這樣線(xiàn)程內部可以使用類(lèi)試:
Class c = cl1.loadClass("com.liu.ClassLoader.TestLoader2");
BaseTest tloader = (BaseTest)c.newInstance();
BaseTest是TestLoader2的父類(lèi),因為BaseTest都是AppClassLoader或父加載器加載的,因此可以達到成功隱式轉換的目的。
對于Tomcat等幾個(gè)處理的Classloader都是自定義并重寫(xiě)了loadclass方法,內部會(huì )更復雜處理。
【最新java classloader詳解】相關(guān)文章:
Java ClassLoader原理詳細分析201603-04
Java基礎知識詳解12-07
最新連詞的種類(lèi)詳解01-04
最新加拿大留學(xué)傳媒專(zhuān)業(yè)詳解03-07
科目一考試最新技巧口訣詳解02-27
2016最新Java認證筆試題及答案01-21