類加載器並行加載類 實戰及代碼解析


類加載器是如何加載一個類的? 類加載器如何實現並行加載類? 帶着這2個問題,我們看下面的內容。



類加載過程實戰

首先,準備一段測試代碼,代碼中使用應用程序類加載器去嘗試加載一個類

public class ClassLoaderDemo {

    public static void main(String[] args) throws Exception {
        System.out.println();
        Class zclass =
               ClassLoader.getSystemClassLoader().loadClass("huangy.hyinterface.Generator");
        System.out.println(zclass);
    }

}

通過打斷點,我們發現最開始是使用應用程序類加載器進行加載
image-20200411101257339

在應用程序類加載器中,首先會嘗試讓其父類加載器(即擴展類加載器)去嘗試加載類
image-20200411101422862

在擴展類加載器中,嘗試讓啓動類加載器去加載器類
image-20200411101500181


類加載過程源碼解析

上面就是標準的雙親委派模型。其具體源碼解析如下:

// 代碼位置  java.lang.ClassLoader#loadClass(java.lang.String, boolean)

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        /*
         * 獲取鎖進行同步
         * 類加載器加載類是會同步的,只不過看同步的粒度是整體串行、還是分段同步
         */
        synchronized (getClassLoadingLock(name)) {

            // 首先,檢查該類是否已經被加載了,加載了則直接返回
            Class<?> c = findLoadedClass(name);

            if (c == null) {

                long t0 = System.nanoTime();

                try {
                    if (parent != null) {
                        // 如果有父類加載器,首先讓父類加載去加載類,即我們常說的雙親委派模型
                        c = parent.loadClass(name, false);
                    } else {
                        /*
                         * 如果沒有父類加載器,則讓啓動類加載器進行加載
                         * 通過調試可知,擴展類加載器ExtClassLoader的parent屬性爲null,則會讓
                         * 啓動類加載器進行加載類
                         */
                        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;
        }
    }

protected Object getClassLoadingLock(String className) {

        // 默認情況下,類加載器本身作爲鎖,只能串行加載
        Object lock = this;

        if (parallelLockMap != null) {

            // 如果註冊成功了,則支持分段鎖,即支持並行加載
            Object newLock = new Object();

            // 對同一個類,返回同一把鎖
            lock = parallelLockMap.putIfAbsent(className, newLock);

            if (lock == null) {
                lock = newLock;
            }
        }
        return lock;
    }

注意上述代碼中,parallelLockMap只有在註冊了的情況下,爲不爲null,也就是可以並行加載類,那麼具體的註冊流程是怎樣的?代碼如下:

// 代碼位置 java.lang.ClassLoader.ParallelLoaders#register

/**
 * 要顯示的調用ParallelLoaders#register方法,纔會進行註冊。
 * 註冊當前類加載器,其父類加載器必須支持並行加載,才能註冊成功,這個爲什麼?
 * 理由很簡單,因爲走的是雙親委派模型,假如子類加載器支持並行,但是父類加載器是串行,那麼實際上整個類加載過程就還是串行的。
 */
static boolean register(Class<? extends ClassLoader> c) {
  synchronized (loaderTypes) {
    if (loaderTypes.contains(c.getSuperclass())) {
      loaderTypes.add(c);
      return true;
    } else {
      return false;
    }
  }
}

/**
 * 判斷是否支持註冊
 */
static boolean isRegistered(Class<? extends ClassLoader> c) {
  synchronized (loaderTypes) {
    return loaderTypes.contains(c);
  }
}

private ClassLoader(Void unused, ClassLoader parent) {
  this.parent = parent;
  if (ParallelLoaders.isRegistered(this.getClass())) {
    // 如果註冊過了,則初始化parallelLockMap,則支持並行加載
    parallelLockMap = new ConcurrentHashMap<>();
    package2certs = new ConcurrentHashMap<>();
    domains =
      Collections.synchronizedSet(new HashSet<ProtectionDomain>());
    assertionLock = new Object();
  } else {
    // no finer-grained lock; lock on the classloader instance
    parallelLockMap = null;
    package2certs = new Hashtable<>();
    domains = new HashSet<>();
    assertionLock = this;
  }
}



並行加載類實戰

/**
 * 並行加載類示例
 * @author huangy on 2019-10-22
 */
public class CustomClassLoaderDemo {

    public static void main(String[] args) throws Exception {

        // 自定義類加載器
        ClassLoader customClassLoader = new CustomClassLoader();

        Class zclass = customClassLoader.loadClass("huangy.hyinterface.Generator");

        System.out.println(zclass);
    }
}


class CustomClassLoader extends ClassLoader {

    @Override
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        // 這裏加載二進制字節流的方式可以自己決定
        return super.loadClass(name, resolve);
    }

    static {
        // 註冊,讓類加載器支持並行加載類
        ClassLoader.registerAsParallelCapable();
    }
}

image-20200411103514938

如圖,註冊之後,parallelLockMap不爲空,即獲取的是類粒度的分段鎖,從而實現了類的並行加載。



總結一下

  • 使用類加載器加載類,則首先會嘗試讓父類加載器去加載類,如果找不到,再由當前類加載器去加載類
  • 加載類的時候,默認是串行的,因爲使用類加載器自身作爲鎖
  • 如果先進行類的註冊,則能實現類的並行加載,從提高程序的啓動速度
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章