• 并行类加载与OSGI类加载


    这回来分析一下OSGI的类加载机制。

    先说一下OSGI能解决什么问题吧。

    记得在上家公司的时候,经常参与上线。上线一般都是增加了一些功能或者修改了一些功能,然后将所有的代码重新部署。过程中要将之前的服务关掉,而且不能让客户访问。虽然每回的夜宵都不错,但还是感觉这个过程很麻烦,很别扭。

    为什么明明只修改了一部分代码,却都要重新来一遍。

    OSGI架构里面,很重要的一个理念就是分模块(bundle)。如果你只是修改了一个模块,就可以只热替换这个模块,不影响其它模块。想想就很有吸引力。要实现这种功能,类加载的委派模型必须大改。像AppClassLoader --》ExtClassLoader --》BootstrapClassLoader这种固定的树形结构,明显不能扩展,不能实现需求。

    OSGI的规范要求每个模块都有自己的类加载器,而模块之间的依赖关系,就形成了各个类加载器之间的委派关系。这种委派关系是动态的,是自由恋爱,而不是指腹为婚。。。。。。

    当然,委派是要依据规则的。这也好理解啊,谈婚论嫁时,女方的家长肯定会问,有房吗、有车吗、有几块腹肌啊。哎,又扯远了。

    当一个模块(bundle)的类加载器遇到需要加载某个类或查找某个资源的请求时,规则步骤如下:

    1)如果在以java.*开头的package中,那么这个请求需要委派给父类加载器

    2)如果在父类委派清单所列明的package中,还是委派给父类加载器

    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类的注册方法。

    类的加载能够并行后,我们启动应用的时候,肯定会更快。

  • 相关阅读:
    angularjs的$on、$emit、$broadcast
    angularjs中的路由介绍详解 ui-route(转)
    ionic入门教程-ionic路由详解(state、route、resolve)(转)
    Cocos Creator 加载使用protobuf第三方库,因为加载顺序报错
    Cocos Creator 计时器错误 cc.Scheduler: Illegal target which doesn't have uuid or instanceId.
    Cocos Creator 构造函数传参警告 Can not instantiate CCClass 'Test' with arguments.
    Cocos Creator 对象池NodePool
    Cocos Creator 坐标系 (convertToWorldSpaceAR、convertToNodeSpaceAR)
    Cocos Creator 常驻节点addPersistRootNode
    Cocos Creator 配合Tiled地图的使用
  • 原文地址:https://www.cnblogs.com/cz123/p/6918708.html
Copyright © 2020-2023  润新知