ClassLoader,即java類(lèi)加載器,主要作用是將class加載到JVM內,同時(shí)它還要考慮class由誰(shuí)來(lái)加載。在說(shuō)java的類(lèi)加載機制之前,還是像前面的博客一樣,先說(shuō)說(shuō)為什么要知道java的類(lèi)加載機制。個(gè)人認為主要有以下幾個(gè)原因:
一般說(shuō)到j(luò )ava的類(lèi)加載機制,都要說(shuō)到“雙親委派模型”(其實(shí)個(gè)人很不理解為什么叫“雙親”,其實(shí)英文叫“parent”)。使用這種機制,可以避免重復加載,當父親已經(jīng)加載了該類(lèi)的時(shí)候,就沒(méi)有必要子ClassLoader再加載一次。JVM根據 類(lèi)名+包名+ClassLoader實(shí)例ID 來(lái)判定兩個(gè)類(lèi)是否相同,是否已經(jīng)加載過(guò)(所以這里可以略微擴展下,可以通過(guò)創(chuàng )建不同的classloader實(shí)例來(lái)實(shí)現類(lèi)的熱部署)。
有個(gè)圖很形象(來(lái)源見(jiàn)參考資料)。
注意:如果想形象地看到j(luò )ava的類(lèi)加載順序,可以在運行java的時(shí)候加個(gè)啟動(dòng)參數,即java –verbose
下面結合上圖來(lái)詳細介紹下java的類(lèi)加載過(guò)程。
很多資料和文章里說(shuō),ExtClassLoader的父類(lèi)加載器是BootStrapClassLoader,其實(shí)這里省掉了一句話(huà),容易造成很多新手(比如我)的迷惑。嚴格來(lái)說(shuō),ExtClassLoader的父類(lèi)加載器是null,只不過(guò)在默認的ClassLoader 的 loadClass 方法中,當parent為null時(shí),是交給BootStrapClassLoader來(lái)處理的,而且ExtClassLoader 沒(méi)有重寫(xiě)默認的loadClass方法,所以,ExtClassLoader也會(huì )調用BootStrapLoader類(lèi)加載器來(lái)加載,這就導致“BootStrapClassLoader具備了ExtClassLoader父類(lèi)加載器的功能”??匆幌孪旅娴拇a就很容易理解上面這一大段話(huà)了。
/** * 查看父類(lèi)加載器 */private static void test1() { ClassLoader appClassLoader = ClassLoader.getSystemClassLoader(); System.out.println('系統類(lèi)裝載器:' + appClassLoader); ClassLoader extensionClassLoader = appClassLoader.getParent(); System.out.println('系統類(lèi)裝載器的父類(lèi)加載器——擴展類(lèi)加載器:' + extensionClassLoader); ClassLoader bootstrapClassLoader = extensionClassLoader.getParent(); System.out.println('擴展類(lèi)加載器的父類(lèi)加載器——引導類(lèi)加載器:' + bootstrapClassLoader);}可以看出ExtensionClassLoader的parent為null。
查看classloader的源碼可以發(fā)現三個(gè)重要的方法:
好,可能有人看了上面會(huì )疑惑,為什么上面說(shuō)自定義classloader是需要重寫(xiě)findClass而不是loadClass或者其他方法。這里建議查看源碼。
protected Class??> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // First, check if the class has already been loaded Class c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { if (parent != null) { c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } if (c == null) { // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime(); c = findClass(name); // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } return c; } }可以看到,JDK已經(jīng)在loadClass方法中幫我們實(shí)現了ClassLoader搜索類(lèi)的判斷方法,當在loadClass方法中搜索不到類(lèi)時(shí),loadClass方法就會(huì )調用findClass方法來(lái)搜索類(lèi),所以我們只需重寫(xiě)該方法即可。
看完這一大串可能有人還是不理解,ok,那我們現在就動(dòng)手寫(xiě)一個(gè)自己的ClassLoader,盡量包含上面三個(gè)方法。
先定義一個(gè)Person接口。
public interface Person { public void say();}再定一個(gè)高富帥類(lèi)實(shí)現這個(gè)接口
public class HighRichHandsome implements Person { @Override public void say() { System.out.println('I don't care whether you are rich or not'); }}好的,開(kāi)胃菜結束,主角來(lái)了,MyClassLoader。
import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.InputStream;public class MyClassLoader extends ClassLoader{ /* * 覆蓋了父類(lèi)的findClass,實(shí)現自定義的classloader */ @Override public Class??> findClass(String name) { byte[] bt = loadClassData(name); return defineClass(name, bt, 0, bt.length); } private byte[] loadClassData(String className) { InputStream is = getClass().getClassLoader().getResourceAsStream( className.replace('.', '/') + '.class'); ByteArrayOutputStream byteSt = new ByteArrayOutputStream(); int len = 0; try { while ((len = is.read()) != -1) { byteSt.write(len); } } catch (IOException e) { e.printStackTrace(); } return byteSt.toByteArray(); }}代碼很簡(jiǎn)單,不解釋了,最后在測試類(lèi)LoaderTest里寫(xiě)個(gè)測試方法。
/** * 父類(lèi)classloader * @throws Exception */private static void test2() throws Exception{ MyClassLoader loader = new MyClassLoader(); Class??> c = loader.loadClass('com.alibaba.classload.HighRichHandsome'); System.out.println('Loaded by :' + c.getClassLoader()); Person p = (Person) c.newInstance(); p.say(); HighRichHandsome man = (HighRichHandsome) c.newInstance(); man.say(); }main方法中調用這個(gè)方法即可。LoaderTest默認構造函數會(huì )設置AppClassLoader為parent, 測試時(shí)執行test2()方法會(huì )發(fā)現HighRichHandsome類(lèi)是委托AppClassLoader加載的,所以AppClassLoader可以訪(fǎng)問(wèn)到,不會(huì )出錯。
但是我們再想一下,如果我們直接加載,不委托給父類(lèi)加載,會(huì )出現什么情況?
/** * 自己的classloader加載 * @throws Exception */private static void test3() throws Exception{ MyClassLoader loader = new MyClassLoader(); Class??> c = loader.findClass('com.alibaba.classload.HighRichHandsome'); System.out.println('Loaded by :' + c.getClassLoader()); Person p = (Person) c.newInstance(); p.say(); //注釋下面兩行則不報錯 HighRichHandsome man = (HighRichHandsome) c.newInstance(); man.say(); }可以看到,悲劇的報錯了。根據class loader命名空間規則,每個(gè)class loader都有自己唯一的命名空間,每個(gè)class loader 只能訪(fǎng)問(wèn)自己命名空間中的類(lèi),如果一個(gè)類(lèi)是委托parent加載的,那么加載后,這個(gè)類(lèi)就類(lèi)似共享的,parent和child都可以訪(fǎng)問(wèn)到這個(gè)類(lèi),因為parent是不會(huì )委托child加載類(lèi)的,所以child加載的類(lèi)parent訪(fǎng)問(wèn)不到。簡(jiǎn)單來(lái)說(shuō),即子加載器的命名空間包含了parent加載的所有類(lèi),反過(guò)來(lái)則不成立。 本例中LoaderTest類(lèi)是AppClassLoader加載的,所以其看不見(jiàn)由MyClassLoader加載的HighRichHandsome類(lèi),但Person接口是可以訪(fǎng)問(wèn)的,所以賦給Person類(lèi)型不會(huì )出錯。
1.Class.forName()
相信大家都寫(xiě)過(guò)連接數據庫的例子,基本上就是加載驅動(dòng),建立連接,創(chuàng )建請求,寫(xiě)prepareStatement,關(guān)閉連接之類(lèi)的。在這里,有一段代碼:
public DbTest() { try { Class.forName('com.mysql.jdbc.Driver');// 加載驅動(dòng) conn = DriverManager.getConnection(url, 'root', '');// 建立連接 stm = conn.createStatement(); // 創(chuàng )建請求 } catch (Exception e) { e.printStackTrace(); }}我相信大家一開(kāi)始的時(shí)候肯定都有些疑惑,就是Class.forName(“com.mysql.jdbc.Driver”),為什么加載驅動(dòng)是Class.forName,而不是ClassLoader的loadClass?為什么這么寫(xiě)就可以加載驅動(dòng)了呢?
其實(shí)Class.forName()是顯示加載類(lèi),作用是要求JVM查找并加載指定的類(lèi),也就是說(shuō)JVM會(huì )執行該類(lèi)的靜態(tài)代碼段。查看com.mysql.jdbc.Driver源碼可以發(fā)現里面有個(gè)靜態(tài)代碼塊,在加載后,類(lèi)里面的靜態(tài)代碼塊就執行(主要目的是注冊驅動(dòng),把自己注冊進(jìn)去),所以主要目的就是為了觸發(fā)這個(gè)靜態(tài)方法。
2.Web容器加載機制
其實(shí)web容器的加載機制這一點(diǎn)我說(shuō)不好,因為沒(méi)看過(guò)諸如tomcat之類(lèi)的源碼,但是根據自己使用感覺(jué)以及查了相關(guān)資料,可以簡(jiǎn)單介紹一下。一般服務(wù)器端都要求類(lèi)加載器能夠反轉委派原則,也就是先加載本地的類(lèi),如果加載不到,再到parent中加載。這個(gè)得細看,我這里只知道這個(gè)概念,所以就不說(shuō)了。JavaEE規范推薦每個(gè)模塊的類(lèi)加載器先加載本類(lèi)加載的內容,如果加載不到才回到parent類(lèi)加載器中嘗試加載。
3.重復加載與回收
一個(gè)class可以被不同的class loader重復加載,但同一個(gè)class只能被同一個(gè)class loader加載一次。見(jiàn)下列代碼:
/** * 對象只加載一次,返回true */private static void test4() { ClassLoader c1 = LoaderTest.class.getClassLoader(); LoaderTest loadtest = new LoaderTest(); ClassLoader c2 = loadtest.getClass().getClassLoader(); System.out.println('c1.equals(c2):'+c1.equals(c2));}類(lèi)的回收。說(shuō)到最開(kāi)始的why,其實(shí)java的回收機制我前面有篇博客說(shuō)的比較詳細,這里就插一句廢話(huà)吧,當某個(gè)classloader加載的所有類(lèi)實(shí)例化的所有對象都被回收了,則該classloader會(huì )被回收。
參考:http://blog.csdn.net/xyang81/article/details/7292380
ps:話(huà)說(shuō)csdn的markdown寫(xiě)出來(lái)的效果實(shí)在不敢恭維,小小的吐槽一下。
聯(lián)系客服