Java類加載機制詳解

類的加載過程

在使用java命令運行主類(main)的時候,首先要經過類加載器將類加載到JVM內存中去。主類在運行過程當中若是用到其餘的類就會逐步加載這些類。jar包裏的類並非一次性加載的,是使用的時候才加載的。java

類加載過程分爲如下幾步:web

加載 》驗證 》準備 》解析 》初始化 》使用 》卸載bootstrap

一、加載:在硬盤上經過IO讀入字節碼文件,使用到類的時候纔會加載,例如調用main()方法,new對象等等。數組

二、驗證:校驗字節碼文件的正確性tomcat

三、準備:給類的靜態變量分配內存,而且賦予默認值安全

四、解析:將符號引用替換爲直接引用,該階段會把一些靜態方法替換爲指向數據所存內存的地址指針或句柄,即靜態連接過程(類加載期間完成)。動態連接是在程序運行期間完成的,將符號引用替換爲直接引用。服務器

五、初始化:對類的靜態變量初始化爲指定值,執行靜態代碼塊。app

 雙親委派機制

雙親委派機制就是在加載某個類的時候會先委託父類加載器(爸爸)加載,父類加載器就會繼續委託其父類加載器(爺爺輩)加載,若是全部父類加載器都沒有找到目標類,則在本身的加載類路徑中查找目標類進行加載。簡言之就是有事情作的時候,先交給爸爸作,爸爸作不了就交給爺爺作,爺爺也作不了就只能本身作了。也就是拼爹機制。jvm

  假設你本身寫了一個類Test.當在加載Test的時候,應用程序類加載器會委託擴展類加載器加載,擴展類加載器會委託啓動類加載器加載,啓動類加載器在加載路徑中沒法找到Test類,就退回給擴展類加載器,擴展類加載器收到回覆,就在本身的加載類路徑中查找該類,找不到就退回給應用程序加載器加載。應用程序類在加載類路徑中查找要加載的類進行加載。jsp

爲何要設置雙親委派機制呢:

一、避免重複加載

二、沙箱安全機制,避免核心API庫被篡改。(好比你本身寫了一個String類,是不會加載的)

 自定義加載器

要實現自定義加載器只須要繼承ClassLoader類,該類有兩個核心方法,

  • loadClass(String, boolean),實現了雙親委派機制,大致邏輯
  1. 首先,檢查一下指定名稱的類是否已經加載過,若是加載過了,就不須要再加載,直接返回。
  2. 若是此類沒有加載過,那麼,再判斷一下是否有父加載器;若是有父加載器,則由父加載器加載(即調用parent.loadClass(name, false);).或者是調用bootstrap類(啓動類)加載器來加載。
  3.  若是父加載器及bootstrap類加載器都沒有找到指定的類,那麼調用當前類加載器
    的findClass方法來完成類加載。還有一個方法是findClass,默認實現是拋出異常,因此咱們自定義類加載器主要是重寫
  • findClass方法。
 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);
     //defineClass將一個字節數組轉爲Class對象,這個字節數組是class文件讀取後最終
    的字節數組。
     return defineClass(name, data, 0, data.length);
     } catch (Exception e) {
       e.printStackTrace();
     throw new ClassNotFoundException();
     }
    }

 }

 public static void main(String args[]) throws Exception {
     MyClassLoader classLoader = new MyClassLoader("D:/test");
     Class clazz = classLoader.loadClass("com.tuling.jvm.User1");
     Object obj = clazz.newInstance();
     Method method= clazz.getDeclaredMethod("sout", null);38 method.invoke(obj, null);
     System.out.println(clazz.getClassLoader().getClass().getName());
   }
 }

 運行結果:
 =======本身的加載器加載類調用方法=======
 com.tuling.jvm.MyClassLoaderTest$MyClassLoader

 

 打破雙親委派機制

在如下幾種狀況下須要打破雙親委派機制:

一、同一個容器裏面部署多個應用,這幾個應用都依賴於同一個第三方類庫的不一樣版本

二、多個應用共享同一個版本的類庫。

三、容器依賴的類庫與應用程序的類庫分開

Tomcat自定義加載器

Tomcat是個web容器, 那麼它要解決什麼問題: 
1. 一個web容器可能須要部署兩個應用程序,不一樣的應用程序可能會依賴同一個第三方類庫的不一樣版本,不能要求同一個類庫在同一個服務器只有一份,所以要保證每一個應用程序的類庫都是獨立的,保證相互隔離。 
2. 部署在同一個web容器中相同的類庫相同的版本能夠共享。不然,若是服務器有10個應用程序,那麼要有10份相同的類庫加載進虛擬機,這是扯淡的。 
3. web容器也有本身依賴的類庫,不能於應用程序的類庫混淆。基於安全考慮,應該讓容器的類庫和程序的類庫隔離開來。 
4. web容器要支持jsp的修改,咱們知道,jsp 文件最終也是要編譯成class文件才能在虛擬機中運行,但程序運行後修改jsp已是司空見慣的事情,不然要你何用? 因此,web容器須要支持 jsp 修改後不用重啓。

Tomcat 若是使用默認的類加載機制行不行? 
答案是不行的。爲何?

一、若是使用默認的類加載器機制,那麼是沒法加載兩個相同類庫的不一樣版本的,默認的累加器是無論你是什麼版本的,只在意你的全限定類名,而且只有一份。

二、默認的類加載器是可以實現的,由於他的職責就是保證惟一性。

三、同1。

四、咱們想咱們要怎麼實現jsp文件的熱修改,jsp 文件其實也就是class文件,那麼若是修改了,但類名仍是同樣,類加載器會直接取方法區中已經存在的,修改後的jsp是不會從新加載的。那麼怎麼辦呢?咱們能夠直接卸載掉這jsp文件的類加載器,因此你應該想到了,每一個jsp文件對應一個惟一的類加載器,當一個jsp文件修改了,就直接卸載這個jsp類加載器。從新建立類加載器,從新加載jsp文件。

 

 

 

CommonClassLoader、CatalinaClassLoader、SharedClassLoader和WebappClassLoader則是Tomcat本身定義的類加載器,它們分別加載/common/*/server/*/shared/*(在tomcat 6以後已經合併到根目錄下的lib目錄下)和/WebApp/WEB-INF/*中的Java類庫。其中WebApp類加載器和Jsp類加載器一般會存在多個實例,每個Web應用程序對應一個WebApp類加載器,每個JSP文件對應一個Jsp類加載器。

  • commonLoader:Tomcat最基本的類加載器,加載路徑中的class能夠被Tomcat容器自己以及各個Webapp訪問;
  • catalinaLoader:Tomcat容器私有的類加載器,加載路徑中的class對於Webapp不可見;
  • sharedLoader:各個Webapp共享的類加載器,加載路徑中的class對於全部Webapp可見,可是對於Tomcat容器不可見;
  • WebappClassLoader:各個Webapp私有的類加載器,加載路徑中的class只對當前Webapp可見;

從圖中的委派關係中能夠看出:

  • CommonClassLoader能加載的類均可以被Catalina ClassLoader和SharedClassLoader使用,從而實現了公有類庫的共用,而CatalinaClassLoader和Shared ClassLoader本身能加載的類則與對方相互隔離。
  • WebAppClassLoader可使用SharedClassLoader加載到的類,但各個WebAppClassLoader實例之間相互隔離。
  • JasperLoader的加載範圍僅僅是這個JSP文件所編譯出來的那一個.Class文件,它出現的目的就是爲了被丟棄:當Web容器檢測到JSP文件被修改時,會替換掉目前的JasperLoader的實例,並經過再創建一個新的Jsp類加載器來實現JSP文件的HotSwap功能。
相關文章
相關標籤/搜索