java classloader原理深究

前面已經寫過一篇關於java classloader的拙文java classloader原理初探html

時隔幾年,再看一遍,以爲有些地方顯得太過蒼白,因而再來一篇:java

完成一個Java類以後,通過javac編譯,會生成一個class文件,這個class文件中包含跟這個類相關的全部基本信息:屬性字段,方法等。這些都屬於一個類的元數據,是不變的部分。在執行過程,則須要根據類的元數據信息生成一個實例對象,這個實例對象能夠根據不一樣場景擁有不一樣狀態。也就是說同一個class對應了運行過程當中的不一樣狀態。(請注意這裏是class,不是object)每個java文件在被編譯以後,編譯器都會給加入一個public的靜態字段叫:class。在程序中,咱們就能夠經過SomeObject.class的方式來獲取一個Class對象。
Java中經過包路徑和類名來肯定一個類,經過java api咱們知道Class中有個方法是getClassLoader(),但若是一個類被兩個不一樣classloader加載, 這個方法就會返回不一樣結果,在這種狀況下,雖然是同一個class,但被不一樣class loader加載,那對應的Class對象就不是同一個。

爲何要設置環境變量:JAVA_HOME, CLASSPATH?JAVA_HOME對應的是java的安裝根路徑,爲了能在系統中隨處都能用到java給咱們提供的命令行工具,因此要把JAVA_HOME/bin的路徑添加到PATH中。可是CLASSPATH呢,爲何須要在CLASSPATH中指定那三個jar(tools.jar, dt.jar, rt.jar)?
先說明一下tools.jar主要是一些java 工具類,能夠從openjdk上下載jdk的源碼,找langtools這個路徑下的文件來看看, 若是想寫一些工具類,好比經過java來分析或編譯java文件,能夠查看一下tools裏面的javac相關的api;dt.jar主要是swing相關的一些類。而rt.jar則是運行時相關的,是java的核心庫中的java類。
再來理解一下java在啓動時類的代理模式加載順序。JAVA中除了Bootstrap class loader 都有一個parent class loader。參閱java 官方對於「 Understanding Extension Class Loading」的說明。裏面提到了三個方面的加載內容:一是rt.jar和i18n.jar等基礎類包;二是擴展包;三就是咱們classpath指定的依賴包。對於第一部分,由Bootstrap class loader負責加載,因此java包中的類的class loader就是bootstrap class loader,而擴展路徑(通常是在jre/lib/ext)下由 extension class loader(ExtClassLoader)負責. 第三部分的加載就須要應用class loader(AppClassLoader)來負責了。通常狀況下,直接經過java啓動時,會註明一下-classpath 或 -cp來標識出依賴包列表(注意,不是一個路徑,而是文件列表)。代碼中負責初始化相關class loader的過程是在sun.misc.Launcher類中, 下面是Launcher的constructor方法中的實現。 public Launcher() {         // Create the extension class loader         ClassLoader extcl;         try {             extcl = ExtClassLoader.getExtClassLoader();         } catch (IOException e) {             throw new InternalError(                 "Could not create extension class loader", e);         }         // Now create the class loader to use to launch the application         try {             loader = AppClassLoader.getAppClassLoader(extcl);         } catch (IOException e) {             throw new InternalError(                 "Could not create application class loader", e);         }         // Also set the context class loader for the primordial thread.         Thread.currentThread().setContextClassLoader(loader); ….. } 對於前面提到的相關加載路徑也都在這個類中有相關代碼,不一一列舉。 Java Api中的ClassLoader類中的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;         }     } 加載某個類時,當前classloader都會先委託parent class loader嘗試加載。這種處理方式,有幾個方面的考慮:一是安全問題,若是攻擊者模擬實現了對應的類如java.lang.String, 當經過loadClass來加載類時,會首先到parent classloader中查找,很明顯能夠在bootstrap class loader中找到,這樣能夠儘量保護最關鍵的代碼。一是冗餘問題,經過這種方式能夠最大限度的下降重複加載。每一個層次的classloader負責對應路徑下的類庫加載,而對於應用實現來講就能夠集中在應用系統中。這裏能夠考慮一下web container的實現,如tomcat。每一個jsp頁面都會最終被編譯成一個class文件,並放到work路徑中,因此每一個web容器都會自建一個classloader來加載指定路徑中的class文件。 經過上面的說明,能夠了解到jvm在啓動過程當中,若是發現有相同包路徑的狀況(不一樣jar,但相同包)下的同名類,則僅會加載一次,主要看哪一個包在最前面。這裏有個問題:爲何是在前面的包能夠起做用,後面不行?若是是由同一個classloader加載還不會被覆蓋麼?其實若是是本身實現的classloader,那就能夠調整這種策略,但java中約定的就是先加載的class會根據起binary name,將class metadata存於永久區,因而後面再有同名的類,均可以被找到,而不須要從新加載。 
相關文章
相關標籤/搜索