Java虛擬機:類加載過程

在馮諾依曼的計算機模型中,任何程序都要加載到內存才能與CPU進行交流。字節碼.class文件一樣須要加載到內存中,才能夠實例化。java

其中,類加載器ClassLoader的使命就是加載.class文件到內存中。數據庫

在加載類時,使用的是Parents Delegation Model,即雙親委派模型bootstrap

Java類加載器是一個運行時核心基礎設施模塊,以下圖,主要是在啓動之初進行類的加載(Load)、連接(Link)、初始化(Init)網絡

第一步,Load階段。數據結構

讀取.class文件流,並轉換爲特定的數據結構,初步校驗cafe babe魔法數、常量池、文件長度、是否有父類等,而後建立對應類的java.lang.Class實例。框架

第二步,Link階段。ide

包括驗證、準備和解析三個步驟。佈局

  • 驗證:是更詳細地校驗,好比final是否合規、類型是否正確、靜態變量是否合理等。
  • 準備:爲靜態變量分配內存,設定默認初始值。
  • 解析:解析類和方法之間相互引用正確性,完成內存佈局。

第三步,Init階段。this

執行類構造器的<clinit>方法。加密

 

類加載器

類加載器相似於原始部落結構,存在權利等級制度。

最高一層是Bootstrap ClassLoader,它是在JVM啓動時建立的,是最根基的類加載器,負責裝載最核心的Java類,好比Object、System、String等。由C++編寫。

第二層是Extension ClassLoader(JDK9以後變爲了Platform ClassLoader,平臺類加載器)。用以加載擴展的系統類,好比XML、加密、壓縮等相關類。Java編寫。

第三層是Application ClassLoader,應用類加載器,主要是加載用戶自定義classpath下的類。

 

雙親委派模型

結合上圖,咱們來看一下ClassLoader的loadClass()方法:

 1 /**
 2     @Param resolve 是否連接
 3     @Param name 類名
 4 */
 5 protected Class<?> loadClass(String name, boolean resolve)
 6         throws ClassNotFoundException
 7 {
 8     synchronized (getClassLoadingLock(name)) {
 9         // 首先檢查該類是否已經被加載過
10         Class<?> c = findLoadedClass(name);
11         if (c == null) {
12             long t0 = System.nanoTime();
13             try {
14                 // 若是父加載器不爲空,就用它的父加載器去加載該類
15                 if (parent != null) {
16                     c = parent.loadClass(name, false);
17                 } else {
18                     // 到這裏的話就是bootstrapClassLoader
19                     // 這是一個本地方法,使用bootstrapClassLoader去加載該類
20                     // 若是加載不了,就返回空
21                     c = findBootstrapClassOrNull(name);
22                 }
23             } catch (ClassNotFoundException e) {
24                
25             }
26 
27             // 若是返回空了,證實其父類加載器不能加載該類
28             if (c == null) {
29 
30                 long t1 = System.nanoTime();
31                 // 使用本身進行加載
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         // 若是resove爲true,就連接
41         if (resolve) {
42             resolveClass(c);
43         }
44         return c;
45     }
46 }

 

低層次的當前類加載器,不能覆蓋更高層次類加載器已經加載過的類。

若是底層的類加載器想要加載一個未知類,要很是有禮貌的向上級詢問:」請問,這個類已經加載了嗎?「,被詢問的高層類首先要回答本身一個問題:我是否加載過此類?若是答案爲否,那麼又向上詢問,直到Bootstrap ClassLoader爲止,若是它也未加載過而且並加載,就會逐層交給下一層類加載器,反應在源碼中就是第28行逐層遞歸退出。

 

咱們看一下Bootstrap全部已經加載的類庫:

URL[] urls = Launcher.getBootstrapClassPath().getURLs();
Arrays.asList(urls).forEach(System.out::println);

 

結果:

file:/D:/Development%20Tools/Java/jdk1.8.0_201/jre/lib/resources.jar
file:/D:/Development%20Tools/Java/jdk1.8.0_201/jre/lib/rt.jar
file:/D:/Development%20Tools/Java/jdk1.8.0_201/jre/lib/sunrsasign.jar
file:/D:/Development%20Tools/Java/jdk1.8.0_201/jre/lib/jsse.jar
file:/D:/Development%20Tools/Java/jdk1.8.0_201/jre/lib/jce.jar
file:/D:/Development%20Tools/Java/jdk1.8.0_201/jre/lib/charsets.jar
file:/D:/Development%20Tools/Java/jdk1.8.0_201/jre/lib/jfr.jar
file:/D:/Development%20Tools/Java/jdk1.8.0_201/jre/classes

 

Bootstrap的加載路徑是能夠追加的,不建議修改或刪除原有的加載路徑。經過-Xbootclasspath/a:參數能夠增長類加載路徑。

若是想在啓動時觀察加載了哪一個jar包中的類,能夠增長-XX:+TraceClassLoading參數,此參數在解決類衝突時很是實用。

 

何時須要自定義類加載器?

(1)隔離加載類。在某些框架內進行中間件與應用的模塊隔離,把類加載到不一樣的環境。

(2)修改類加載方式。出了Bootstrap外,其餘的加載並不是都要引入。

(3)擴展加載源。好比從數據庫、網絡進行加載。

(4)防止源碼泄露。Java代碼容易被編譯和篡改,能夠進行編譯加密。那麼類加載器也須要自定義。

實現自定義類加載器的步驟:繼承ClassLoader,重寫findClass()方法,調用defineClass()方法。下面是一個例子:

public class MyClassLoader extends ClassLoader {

    private String path;
    private String name;

    public MyClassLoader(String path, String name) {
        this.path = path;
        this.name = name;
    }

    @Override
    public Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] data = null;
        try {
            data = getClassBytes(name);
            return defineClass(name, data, 0, data.length);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

// 獲取字節碼流
private byte[] getClassBytes(String className) throws IOException { InputStream in = null; ByteArrayOutputStream baos = null; try { int len = 0; byte[] bytes = new byte[1024]; in = new FileInputStream(new File(path + className + ".class")); baos = new ByteArrayOutputStream(); while ((len = in.read(bytes)) != -1) { baos.write(bytes, 0, len); baos.flush(); } } catch (Exception e) { }finally { baos.close(); in.close(); } return baos.toByteArray(); } } public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException { MyClassLoader classLoader = new MyClassLoader("E:\\","my classloader"); Class clz = classLoader.findClass("Dog"); Field[] fields = clz.getDeclaredFields(); System.out.println(Arrays.toString(fields)); }
相關文章
相關標籤/搜索