天天看點

徹底剖析JVM類加載機制(二)

文章目錄

  • 1、往期位址
  • 2、Tomcat如何打破雙親委派機制
    • 2.1、以Tomcat類加載為例,Tomcat 如果使用預設的雙親委派類加載機制行不行?
  • 3、Tomcat自定義加載器詳解
  • 4、模拟實作Tomcat的webappClassLoader加載自己war包應用内不同版本類實作互相共存與隔離
  • 5、模拟實作Tomcat的JasperLoader熱加載

徹底剖析JVM類加載機制(一)

Tomcat需要解決什麼問題?	1. 一個web容器可能需要部署兩個應用程式,不同的應用程式可能會依賴同一個第三方類庫的不同版本,不能要求同一個類庫在同一個
	伺服器隻有一份,是以要保證每個應用程式的類庫都是獨立的,保證互相隔離。 
	2. 部署在同一個web容器中相同的類庫相同的版本可以共享。否則,如果伺服器有10個應用程式,那麼要有10份相同的類庫加載進虛拟機。 
	3. web容器也有自己依賴的類庫,不能與應用程式的類庫混淆。基于安全考慮,應該讓容器的類庫和程式的類庫隔離開來。 
	4. web容器要支援jsp的修改,我們知道,jsp 檔案最終也是要編譯成class檔案才能在虛拟機中運作,但程式運作後修改jsp已經
	是司空見慣的事情, web容器需要支援 jsp 修改後不用重新開機。

是以答案:Tomcat 如果使用預設的雙親委派類加載機制是不行的!!!

為什麼不行?	1、第一個問題,如果使用預設的類加載器機制,那麼是無法加載兩個相同類庫的不同版本的,預設的類加器是不管你是什麼版本的,隻在乎你的全限定類名,并且隻有一份。	2、第二個問題,預設的類加載器是能夠實作的,因為他的職責就是保證唯一性。	3、第三個問題和第一個問題一樣。	4、第四個問題,我們想我們要怎麼實作jsp檔案的熱加載,jsp 檔案其實也就是class檔案,那麼如果修改了,但類名還是一樣,
	類加載器會直接取方法區中已經存在的,修改後的jsp是不會重新加載的。那麼怎麼辦呢?我們可以直接解除安裝掉這jsp檔案的類加載器,
	是以你應該想到了,每個jsp檔案對應一個唯一的類加載器,當一個jsp檔案修改了,就直接解除安裝這個jsp類加載器。重新建立類加載器,重新加載jsp檔案。      
徹底剖析JVM類加載機制(二)
tomcat的幾個主要類加載器:	1、commonLoader:Tomcat最基本的類加載器,加載路徑中的class可以被Tomcat容器本身以及各個Webapp通路;	2、catalinaLoader:Tomcat容器私有的類加載器,加載路徑中的class對于Webapp不可見;	3、sharedLoader:各個Webapp共享的類加載器,加載路徑中的class對于所有Webapp可見,但是對于Tomcat容器不可見;	4、WebappClassLoader:各個Webapp私有的類加載器,加載路徑中的class隻對目前Webapp可見,比如加載war包裡相關的類,
	每個war包應用都有自己的WebappClassLoader,實作互相隔離,比如不同war包應用引入了不同的spring版本,這樣實作就能加載各自的spring版本;
	
從圖中的委派關系中可以看出:	1、CommonClassLoader能加載的類都可以被CatalinaClassLoader和SharedClassLoader使用,
	進而實作了公有類庫的共用,而CatalinaClassLoader和SharedClassLoader自己能加載的類則與對方互相隔離。	2、WebAppClassLoader可以使用SharedClassLoader加載到的類,但各個WebAppClassLoader執行個體之間互相隔離。	3、而JasperLoader的加載範圍僅僅是這個JSP檔案所編譯出來的那一個.Class檔案,它出現的目的就是為了被丢棄:
	當Web容器檢測到JSP檔案被修改時,會替換掉目前的JasperLoader的執行個體,并通過再建立一個新的Jsp類加載器來實作JSP檔案的熱加載功能。      

由此可見,tomcat 這種類加載機制違背了java 推薦的雙親委派模型了嗎?答案是:違背了。

很顯然,tomcat 不是這樣實作,tomcat 為了實作隔離性,沒有遵守這個約定,每個webappClassLoader加載自己的目錄下的class檔案,不會傳遞給父類加載器,打破了雙親委派機制。

public class MyClassLoaderTest {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;}protected Class<?> findClass(String name) throws ClassNotFoundException {try {byte[] data = loadByte(name);return defineClass(name, data, 0, data.length);} catch (Exception e) {e.printStackTrace();throw new ClassNotFoundException();}}/**
         * 重寫類加載方法,實作自己的加載邏輯,不委派給雙親加載
         * @param name
         * @param resolve
         * @return
         * @throws ClassNotFoundException
         */protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException {synchronized (getClassLoadingLock(name)) {// First, check if the class has already been loadedClass<?> c = findLoadedClass(name);if (c == null) {// If still not found, then invoke findClass in order// to find the class.long t1 = System.nanoTime();//非自定義的類還是走雙親委派加載if (!name.startsWith("com.zhz.jvm")){c = this.getParent().loadClass(name);}else{c = findClass(name);}// this is the defining class loader; record the statssun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}if (resolve) {resolveClass(c);}return c;}}}public static void main(String args[]) throws Exception {MyClassLoader classLoader = new MyClassLoader("D:/test");Class clazz = classLoader.loadClass("com.zhz.jvm.User1");Object obj = clazz.newInstance();Method method= clazz.getDeclaredMethod("sout", null);method.invoke(obj, null);System.out.println(clazz.getClassLoader());
        System.out.println();MyClassLoader classLoader1 = new MyClassLoader("D:/test1");Class clazz1 = classLoader1.loadClass("com.zhz.jvm.User1");Object obj1 = clazz1.newInstance();Method method1= clazz1.getDeclaredMethod("sout", null);method1.invoke(obj1, null);System.out.println(clazz1.getClassLoader());}}運作結果:=======自己的加載器加載類調用方法=======com.zhz.jvm.MyClassLoaderTest$MyClassLoader@266474c2=======另外一個User1版本:自己的加載器加載類調用方法=======com.zhz.jvm.MyClassLoaderTest$MyClassLoader@15d3c458