並行類加載與OSGI類加載

這回來分析一下OSGI的類加載機制。java

先說一下OSGI能解決什麼問題吧。緩存

記得在上家公司的時候,常常參與上線。上線通常都是增長了一些功能或者修改了一些功能,而後將全部的代碼從新部署。過程當中要將以前的服務關掉,並且不能讓客戶訪問。雖然每回的夜宵都不錯,但仍是感受這個過程很麻煩,很彆扭。安全

爲何明明只修改了一部分代碼,卻都要從新來一遍。多線程

OSGI架構裏面,很重要的一個理念就是分模塊(bundle)。若是你只是修改了一個模塊,就能夠只熱替換這個模塊,不影響其它模塊。想一想就頗有吸引力。要實現這種功能,類加載的委派模型必須大改。像AppClassLoader --》ExtClassLoader --》BootstrapClassLoader這種固定的樹形結構,明顯不能擴展,不能實現需求。架構

OSGI的規範要求每一個模塊都有本身的類加載器,而模塊之間的依賴關係,就造成了各個類加載器之間的委派關係。這種委派關係是動態的,是自由戀愛,而不是指腹爲婚。。。。。。app

固然,委派是要依據規則的。這也好理解啊,談婚論嫁時,女方的家長確定會問,有房嗎、有車嗎、有幾塊腹肌啊。哎,又扯遠了。dom

當一個模塊(bundle)的類加載器遇到須要加載某個類或查找某個資源的請求時,規則步驟以下:ide

1)若是在以java.*開頭的package中,那麼這個請求須要委派給父類加載器ui

2)若是在父類委派清單所列明的package中,仍是委派給父類加載器this

3)若是在import-package標記描述的package中,委派給導出這個包的bundle的類加載器

4)若是在require-bundle導入的一個或多個bundle的包中,就好安裝require-bundle指定的bundle清單順序逐一委派給對應bundle的類加載器

5 )搜索bundle內部的classpath

6)搜索每一個附加的fragment bundle的classpath

7)若是在某個bundle已經聲明導出的package中,或者包含在已經聲明導入(import-package或require-bundle)的package中,搜索終止

8)若是在某個使用dynamicimport-package聲明導入的package中,嘗試在運行時動態導入這個package

9)若是能夠肯定找到一個合適的完成動態導入的bundle,委派給該bundle的類加載器

 

上面這部分徹底照抄周志明的著做《深刻理解OSGI》。規則裏面的父類加載器、bundle等概念,讀者均可以從書中找到完整的講解,我這裏就不展開了。

根據這個規則,全部的bundle之間的類加載造成了錯綜複雜的網狀結構,再也不是一沉不變的單一的樹狀結構。

可是網狀結構,會有一個致命的問題。在jdk1.6包括以前,ClassLoader的類加載方法是synchronized。

 

protected synchronized Class<?> loadClass(String name, boolean resolve)

 

咱們想象一個場景:bundle A 和 bundle B 互相引用了對方的package。這樣在A加載B的包時,A在本身的類加載器的loadClass方法中,會最終調用到B的類加載器的loadClass方法。也就是說,A首先鎖住本身的類加載器,而後再去申請B的類加載器的鎖;當B加載A的包時,正好相反。這樣,在多線程下,就會產生死鎖。你固然可讓全部的類加載過程在單線程裏按串行的方式完成,安全是安全,可是效率過低。

 

由此,引出了本文的另外一個主題---並行類加載。

synchronized方法鎖住的是當前的對象,在這種狀況下,調用loadClass方法去加載一個類的時候,鎖住的是當前的類加載器,也就不能再用這個類加載器去加載別的類。效率過低,並且容易出現死鎖。

因而設計jdk的大牛,對這種模式進行了改進。大牛就是大牛!!!

看看jdk1.6以後的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;
        }
    }

 

synchronized移到了方法內的代碼塊中,也就是說再也不是簡單的鎖定當前類加載器,而是鎖定一個生成的對象。

那麼這個充當鎖的對象是如何生成的?

    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是一個ConcurrentHashMap,putIfAbsent(K, V)方法查看K和V是否已經對應,是的話返回V,不然就將K,V對應起來,返回null。

第一個if判斷裏面的邏輯,一目瞭然:對每一個className關聯一個鎖,並將這個鎖返回。也就是說,將鎖的粒度縮小了。只要類名不一樣,加載的時候就是徹底並行的。這與ConcurrentHashMap實現裏面的分段鎖,目的是同樣的。

我這裏有2個問題但願讀者思考一下:

1)爲何不直接用className這個字符串充當鎖對象  

2)爲何不是直接new一個Object對象返回,而是用一個map將className和鎖對象緩存起來

 

上面的方法中還別有洞天,爲何要判斷parallelLockMap是否爲空,爲何還有可能返回this,返回this的話不就是又將當前類加載器鎖住了嗎。這裏返回this,是爲了向後兼容,由於之前的版本不支持並行。有疑問就看源碼,

 

    // Maps class name to the corresponding lock object when the current
    // class loader is parallel capable.
    // Note: VM also uses this field to decide if the current class loader
    // is parallel capable and the appropriate lock object for class loading.
    private final ConcurrentHashMap<String, Object> parallelLockMap;



    private ClassLoader(Void unused, ClassLoader parent) {
        this.parent = parent;
        if (ParallelLoaders.isRegistered(this.getClass())) {
            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;
        }
    }

 

可見,對於parallelLockMap的處理一開始就分紅了2種邏輯:若是將當前類加載器註冊爲並行類加載器,就爲其賦值;不然就一直爲null。

ParallelLoaders是ClassLoader的內部類

 

    /**
     * Encapsulates the set of parallel capable loader types.
     */
    private static class ParallelLoaders {
        private ParallelLoaders() {}

        // the set of parallel capable loader types
        private static final Set<Class<? extends ClassLoader>> loaderTypes =
            Collections.newSetFromMap(
                new WeakHashMap<Class<? extends ClassLoader>, Boolean>());
        static {
            synchronized (loaderTypes) { loaderTypes.add(ClassLoader.class); }
        }

        /**
         * Registers the given class loader type as parallel capabale.
         * Returns {@code true} is successfully registered; {@code false} if
         * loader's super class is not registered.
         */
        static boolean register(Class<? extends ClassLoader> c) {
            synchronized (loaderTypes) {
                if (loaderTypes.contains(c.getSuperclass())) {
                    // register the class loader as parallel capable
                    // if and only if all of its super classes are.
                    // Note: given current classloading sequence, if
                    // the immediate super class is parallel capable,
                    // all the super classes higher up must be too.
                    loaderTypes.add(c);
                    return true;
                } else {
                    return false;
                }
            }
        }

        /**
         * Returns {@code true} if the given class loader type is
         * registered as parallel capable.
         */
        static boolean isRegistered(Class<? extends ClassLoader> c) {
            synchronized (loaderTypes) {
                return loaderTypes.contains(c);
            }
        }
    }

 

原來,一個類加載器想要成爲一個並行類加載器,是須要本身註冊的,看看註冊方法

    @CallerSensitive
    protected static boolean registerAsParallelCapable() {
        Class<? extends ClassLoader> callerClass =
            Reflection.getCallerClass().asSubclass(ClassLoader.class);
        return ParallelLoaders.register(callerClass);
    }

最終仍是調用了內部類的註冊方法。源碼在上面,能夠看到,一個類加載器要想註冊,它的父類必須已經註冊了,也就是說從繼承路徑上的全部父類都必須是並行類加載器。並且一開始,就把ClassLoader這個類註冊進去了。

我有個疑問,這裏有父類的什麼事呢,光註冊本身這個類就行了呀。想了半天,仍是不明白,是有關於安全嗎?哎,大牛就是大牛,哈哈。讀者若有明白的,請直言相告。

 

最後,來看看並行類加載在Tomcat上的應用。本來WebappClassLoader沒有註冊,只能串行加載類。後來,是阿里意識到了這個問題,解決方案被Tomcat採納。

 

    static {
        // Register this base class loader as parallel capable on Java 7+ JREs
        Method getClassLoadingLockMethod = null;
        try {
            if (JreCompat.isJre7Available()) {
                final Method registerParallel =
                        ClassLoader.class.getDeclaredMethod("registerAsParallelCapable");
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    @Override
                    public Void run() {
                        registerParallel.setAccessible(true);
                        return null;
                    }
                });
                registerParallel.invoke(null);
                getClassLoadingLockMethod =
                        ClassLoader.class.getDeclaredMethod("getClassLoadingLock", String.class);
            }
        } catch (Exception e) {
            // ignore
        }

 

這段代碼出如今WebappClassLoader類的父類WebappClassLoaderBase裏,經過反射調用了ClassLoader類的註冊方法。

類的加載可以並行後,咱們啓動應用的時候,確定會更快。

相關文章
相關標籤/搜索