詳解class的加載過程


一、Java從編碼到執行

首先我們來看一下Java是如何從編碼到執行的呢? 我們有一個x.java文件通過執行javac命令可以變成x.class文件,當我們調用Java命令的時候class文件會被裝載到內存中,這個過程叫做classloader。一般情況下我們自己寫代碼的時候會用到Java的類庫,所以在加載的時候也會把Java類庫相關的類也加載到內存中。裝載完成之后會調用字節碼解釋器和JIT即時編譯器來進行解釋和編譯,編譯完之后由執行引擎開始執行,執行引擎下面對應的就是操作系統硬件了。下圖是大體的流程:

Java叫做跨平台的語言,JVM可以稱之為跨語言的平台;

有個問題:java是解釋執行還是編譯執行?答:解釋和編譯是可以混合的,特別常用的代碼或則是代碼用到的次數特別多的時候,會把一個即時編譯做成本地編譯,這樣會很大程度上的提高效率。

Java虛擬機是如何做到這么多語言都可以在上面運行,關鍵在於class文件,任何語言只要能編譯成class文件,並且符合class文件的規范你就可以放在Java虛擬機上去運行。

二、詳解class文件的加載過程

接下來主要講的是一個class文件是怎么從硬盤上到內存中,並開始執行的。

類加載主要有三個過程:loading 、linking 、initializing;其中linking又分為三個步驟:verification 、preparation 、resolution;

 

1、首先Loading是什么意思呢?是把一個class問價load到內存中去;

2、接下來是Linking分為了三小步:

  • verification  是用來校驗加載進來的class文件是否符合class文件標准,如果不符合直接就會被拒絕了;
  • preparation  是將class文件靜態變量賦默認值而不是初始值,例如static int i =8;這個步驟並不是將i賦值為8,而是賦值為默認值0;
  • resolution  是把class文件常量池中用到的符號引用轉換成直接內存地址,可以訪問到的內容;

3、initializing  成為初始化,靜態變量在這個時候才會被賦值為初始值;

下面為類加載過程的簡化圖:

 

 

 

類加載器的加載過程是分成不同的層次來加載的,不同的類加載器來加載不同的class文件,  Bootstrap >Extension>Application>Custom(自定義類加載器)

1、第一個類加載器的層次為:Bootstrap 稱為啟動類加載器,是Java類加載層次中最頂層的類加載器,負責加載JDK中的核心類庫。

2、第二個類加載器的層次為:Extension 是用來加載擴展類的,主要負責加載Java的擴展類庫,默認加載JAVA_HOME/jre/lib/ext/目錄下的所有jar包。

3、第三個類加載器的層次為:Application 又稱為系統類加載器,負責在JVM啟動時,加載來自在命令java中的classpath或者java.class.path系統屬性或者CLASSPATH操作系統屬性所指定的JAR類包和類路徑。

4、第三個類加載器的層次為:CustomClassLoader(自定義加載器)  

 1 package com.example.demo.classloader;
 2 
 3 public class ClassLoaderScope {
 4     public static void main(String[] args) {
 5         System.out.println("-------------------Bootstrap加載類-------------------");
 6         String property = System.getProperty("sun.boot.class.path");
 7         String s = property.replaceAll(";", System.lineSeparator());
 8         System.out.println(s);
 9 
10         System.out.println("-------------------Ext加載類-------------------");
11 
12         String property1 = System.getProperty("java.ext.dirs");
13         String s1 = property1.replaceAll(";", System.lineSeparator());
14         System.out.println(s1);
15 
16         System.out.println("-------------------App加載類-------------------");
17 
18         String property2 = System.getProperty("java.class.path");
19         String s2 = property2.replaceAll(";", System.lineSeparator());
20         System.out.println(s2);
21     }
22 }
23         /**輸出結果只截取了部分*/
24         //E:\JDK\jdk1.8\jre\lib\resources.jar
25         //E:\JDK\jdk1.8\jre\lib\rt.jar
26         //E:\JDK\jdk1.8\jre\lib\sunrsasign.jar
27         //E:\JDK\jdk1.8\jre\lib\jsse.jar
28         //E:\JDK\jdk1.8\jre\lib\jce.jar
29         //E:\JDK\jdk1.8\jre\lib\charsets.jar
30         //E:\JDK\jdk1.8\jre\lib\jfr.jar
31         //E:\JDK\jdk1.8\jre\classes
32         //----------------------------------------------
33         //E:\JDK\jdk1.8\jre\lib\ext
34         //C:\Windows\Sun\Java\lib\ext
35         //----------------------------------------------
36         //E:\JDK\jdk1.8\jre\lib\charsets.jar
37         //E:\JDK\jdk1.8\jre\lib\deploy.jar
38         //E:\JDK\jdk1.8\jre\lib\ext\access-bridge-64.jar
39         //E:\JDK\jdk1.8\jre\lib\ext\cldrdata.jar
40         //E:\JDK\jdk1.8\jre\lib\ext\dnsns.jar
41         //E:\JDK\jdk1.8\jre\lib\ext\jaccess.jar
42         //E:\JDK\jdk1.8\jre\lib\ext\jfxrt.jar

 

特別注意一點這個的層級關系並沒有繼承的關系在里面,只是單單純純的語法上的繼承;

下圖為類加載的一個全過程:

用比較通俗的話來解釋這個過程,當有一個類需要被加載時,首先要判斷這個類是否已經被加載到內存,判斷加載與否的過程是有順序的,如果有自己定義的類加載器,會先到custom class loader 的cache(緩存)中去找是否已經加載,若已加載直接返回結果,否則到App的cache中查找,如果已經存在直接返回,如果不存在,到Extension中查找,存在直接返回,不存在繼續向父加載器中尋找直到Bootstrap頂層,如果依然沒找到,那就是沒有加載器加載過這個類,需要委派對應的加載器來加載,先看看這個類是否在自己的加載范圍內,如果是直接加載返回結果,若不是繼續向下委派,以此類推直到最下級,如果最終也沒能加載,就會直接拋異常 ClassNotFoundException,這就是雙親委派模式。

理解雙親委派模式:

1、父加載器:不是類加載器的加載器,也不是類加載器的父類加載器(此處意思是沒有父類與子類之間的繼承關系)。

 1 package com.example.demo.classloader;
 2 
 3 /**
 4  * 驗證了父加載器不是加載器的加載器
 5  */
 6 public class ParentAndChild {
 7     public static void main(String[] args) {
 8         //AppClassLoader
 9         ClassLoader classLoader = ParentAndChild.class.getClassLoader();
10         System.out.println(classLoader);
11 
12         //null  這里AppClassLoader的加載器不是ExtClassLoader  而是Bootstrap
13         ClassLoader appclassLoader = ParentAndChild.class.getClassLoader().getClass().getClassLoader();
14         System.out.println(appclassLoader);
15 
16         //ExtClassLoader   AppClassLoader的父加載器是ExtClassLoader
17         ClassLoader parent = ParentAndChild.class.getClassLoader().getParent();
18         System.out.println(parent);
19 
20         //null
21         ClassLoader parentparent = ParentAndChild.class.getClassLoader().getParent().getParent();
22         System.out.println(parentparent);
23 
24         //null
25         ClassLoader parentparentparent = ParentAndChild.class.getClassLoader().getParent().getParent().getParent();
26         System.out.println(parentparent);
27 
28         /**輸出結果*/
29         //sun.misc.Launcher$AppClassLoader@18b4aac2
30         //null
31         //sun.misc.Launcher$ExtClassLoader@23fc625e
32         //null
33         //Exception in thread "main" java.lang.NullPointerException at com.example.demo.classloader.ParentAndChild.main(ParentAndChild.java:22)
34     }
35 }

2、雙親委派:其工作原理的是,如果一個類加載器收到了類加載請求,並不會直接去加載,而是自下而上的向頂層類加載器查找是否已經被加載了,如果被加載就不用進行加載,如果未被加載過,則會自上而下的檢查是否屬於自己加載的范圍,如果屬於則加載,如果不屬於則向下委托,直到類被加載進來才能叫做成功,如果加載不成功就會拋異常classnotfoundexeption,這就叫做雙親委派。

3、為什么要搞雙親委派模式?

主要是為了安全,這里可以使用反證法,如果任何類加載器都可以把class加載到內存中,我們就可以自定義類加載器來加載Java.lang.string。在打包時可以把密碼存儲為String對象,偷偷摸摸的把密碼發送到自己的郵箱,這樣會造成安全問題。

三、自定義類加載器

 1 package com.example.demo.classloader;
 2 
 3 public class ClassLoaderByHand {
 4     public static void main(String[] args) throws ClassNotFoundException {
 5         Class<?> clazz = ClassLoaderByHand.class.getClassLoader().
 6                 loadClass("com.example.demo.threaddemo.juc_002.Account");
 7         String name = clazz.getName();
 8         System.out.println(name);
 9         
10     }
11 }
12 
13    /**
14     * 輸出結果
15     */
16    //com.example.demo.threaddemo.juc_002.Account

代碼運行結果可以看出,就是你要加載一個類你只要調用classLoader中的 loadClass()方法就能把這個類加載到內存中,加載完成之后會給你返回一個Class類的對象。

在硬盤上找到這個類的源碼,把它load到內存,與此同時生成一個Class對象,上述的小程序是通過 ClassLoaderByHand 找到他的加載器AppClassLoader 然后調用它的loadClass()方法,讓它幫我們把 Account類加載進來,返回一個clazz對象,使用clazz.getName()方法正常返回Account類。

什么時候我們需要自己定義去加載一個類?

熱部署時就是先把之前加載的類給干掉 ,然后使用的自定義類加載器來進行重新加載

spring的動態代理,一個新的class 當需要的時候就會把它load到內存中

 

我們還是來看一下源碼吧,加載過程最主要的還是ClassLoader中的loaderClass()方法:

 結合上面給的類加載過程的圖解一起看會更容易一些;

 1 protected Class<?> loadClass(String name, boolean resolve)
 2             throws ClassNotFoundException
 3     {
 4         synchronized (getClassLoadingLock(name)) {
 5             /**
 6              * 在加載之前先調用findLoadedClass()方法查看是否已經加載過此類
 7              * 若加載過 返回該對象
 8              * 如果未加載則返回null 進行下一步
 9              */
10             // First, check if the class has already been loaded
11             Class<?> c = findLoadedClass(name);
12             if (c == null) {
13                 long t0 = System.nanoTime();
14                 try {
15                     //判斷有無父加載器 如果不為空說明還未到頂層Bootstrap遞歸調用loadClass()
16                     if (parent != null) {
17                         c = parent.loadClass(name, false);
18                     } else {
19                         //如果沒有父加載器說明調用的加載器為Bootstrap Class Loader, 在此加載器內存中查找是否已經加載
20                         c = findBootstrapClassOrNull(name);
21                     }
22                 } catch (ClassNotFoundException e) {
23                     // ClassNotFoundException thrown if class not found
24                     // from the non-null parent class loader
25                 }
26                 //若以上的操作都沒成功加載此類
27                 if (c == null) {
28                     // If still not found, then invoke findClass in order
29                     // to find the class.
30                     long t1 = System.nanoTime();
31                     //調用自己的findClass()
32                     c = findClass(name);
33 
34                     // this is the defining class loader; record the stats
35                     sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
36                     sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
37                     sun.misc.PerfCounter.getFindClasses().increment();
38                 }
39             }
40             if (resolve) {
41                 resolveClass(c);
42             }
43             return c;
44         }
45     }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM