JVM之Class加載

load

我們編譯後的.class文件是如何被JVM加載進入內存的呢,中間經過了哪些個步驟呢?如下圖:
在這裏插入圖片描述
主要是經過這麼幾個步驟:

  • loading加載:從磁盤讀取xxx.class文件進入內存
  • linking
    - verifiy驗證階段:驗證class文件是否已“cafababe”開頭
    - prepare準備階段:對類的成員變量開闢空間,將class中的所有成員變量(靜態和非靜態)賦值爲默認值,不執行任何初始化操作
    - resolute解析階段:爲類、接口、方法、成員變量的符號引用定位直接引用,也即與外部其他class建立引用關係
  • init 初始化:將靜態成員變量的默認值替換成初始值

如現有A.class文件,內部定義了一個靜態成員變量B=1,其成員變量B加載過程如下:在prepare階段,先給B賦默認值0,在init階段,再給B賦初始值1。

那麼,當JVM將class文件讀取進內存之後,內存中有哪些東西呢?

  1. 將Class元數據信息保存在matespace上(1.8之後,1.8之前是保存在老年代)
類型 保存位置
靜態成員變量 方法區的靜態部分
靜態方法 方法區的靜態部分
非靜態方法 方法區的非靜態部分
靜態代碼塊 方法區的靜態部分
構造代碼塊 方法區的靜態部分
  1. 創建當前class文件的Class對象,無論我們後面在代碼中new出了多少個實例,JVM中對應的Class對象,始終只有一個。

ClassLoader

Java中有4種ClassLoader,自上而下依次爲Bootstrap CLassloder、ExtClassLoader、AppClassLoader、CustomClassLoader(自定義類加載器)

那這4中加載器,它們之間的關係是什麼,各自的作用域又是什麼呢?要搞清楚這個問題,我們得分析下源碼:
Launcher.class部分源碼如下:

private static String bootClassPath = System.getProperty("sun.boot.class.path");
static class AppClassLoader extends URLClassLoader {
public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException {
            final String var1 = System.getProperty("java.class.path");
            ...
  }
}

static class ExtClassLoader extends URLClassLoader {
	 private static File[] getExtDirs() {
            String var0 = System.getProperty("java.ext.dirs");
            ...
     }
}

通過源碼,我們可以看到有三處定義了uri,這實際便是幾個加載器的作用域,即

  • Bootstrap CLassloder, 負責加載sun.boot.class.path路徑下的類,如lib/rt.jar,charset.jar等核心類庫,此部分功能由c++實現,我們代碼中這部分調用getClassLoader()方法,其返回的是個null,是個跟加載器
  • ExtClassLoader ,負責加載java.ext.dirs路徑下的類,負責加載擴展包
  • AppClassLoader,負責加載java.class.path路徑下的類
  • CustomClassLoader,可自定義加載某個路徑下的類以及加載方式。

雙親委派

JVM沒有規定一個Class文件具體啥時候需要被加載,具體時間由各自的虛擬機決定。常用的Hotspot採用懶加載方式,即當需要用到這個Class的時候纔將該類加載進內存,加載流程是什麼樣的呢?這裏面就會涉及類加載的雙親委派機制了,我們以最全流程爲例講解:
在這裏插入圖片描述
現有一個Class需要加載進內存,使用CustomClassLoader。

  1. CustomClassLoader查看自身是否有加載過這個Class,如果有則不再加載,直接返回,如果沒有加載,轉2
  2. CustomClassLoader詢問父加載器AppClassLoader是否有加載過這個class,如果父加載器有加載過,直接返回,如果父加載器也沒有加載過,轉3
  3. AppClassLoader向父加載器ExtClassLoader詢問是否有加載過,同理,如果沒有加載過,或者加載不成功,轉4
  4. ExtClassLoader詢問父加載器Bootstrap,結果還是一樣沒有加載過,且自身不負責加載該類,Bootstrap通知子加載器ExtClassLoader,要求ExtClassLoader自己嘗試加載
  5. ExtClassLoader嘗試加載該類,發現該類作用域不在自己的管轄內,不能加載,於是再通知自己的子加載器AppClassLoader,要求AppClassLoader自己嘗試加載
  6. AppClassLoader加載失敗,通知CustomClassLoader自己加載
  7. CustomClassLoader負責加載該class進內存

通過上面的步驟可以得知,所謂雙親委派,就是在需要加載一個新的Class的時候,需要向父加載器一層層的問詢是否已經加載過,如果都沒有加載過,則由父加載器一層層通知子加載器,由子加載器嘗試加載class。

Java爲什麼要這麼設計呢?主要是爲了安全,設想這樣一種場景,我們自定義了一個加載器,用來加載String類,如果直接走我們自定義的加載器,我們在加載器內部收集所有string數據上傳,如果這些數據正好是用戶的用戶名和密碼,那我們就可以輕鬆拿到每個人的用戶名和密碼了。
還有一點,爲了避免重複加載,如果一個類已經被父加載器加載過了,內存中已經存在了一個Class對象,這時候直接用就可以了,不需要再次加載。

Java是如何實現雙親委派的呢?源碼如下:

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;
        }
    }

其實很簡單,就是通過遞歸的方式,先在 parent.loadClass中問詢是否有加載過該類,如過parent沒有加載過,且嘗試加載失敗,則當前的子加載器自己嘗試加載該class。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章