精华内容
下载资源
问答
  • osgi容器中有三种classloader:各个bundle都有自己的classLoader,osgi容器也有一个框架级的...如果需要加载一个,那么就需要在这三个classloader之间挑出一个合适的classloader来加载类。本文将根据equinox和f...

    osgi容器中有三种classloader:各个bundle都有自己的classLoader,osgi容器也有一个框架级的classloader,这些classloader也不同于启动osgi容器的classloader(一般是app classloader)。如果需要加载一个类,那么就需要在这三个classloader之间挑出一个合适的classloader来加载类。本文将根据equinox和felix源码肤浅的描述一下osgi类加载顺序。

    osgi规范中类加载顺序的定义

    首先osgi类加载顺序不是开发osgi框架的人定的,而是osgi规范定的,但是每个框架的实现可能有所不同(规范详见osgi R4.3中的第3.9.4节 《Overall Search Order》):

    在类和资源加载过程中,框架必须遵循以下规则。当请求一个bundle类加载器进行类加载或者资源的查找,查找必须按照以下顺序执行:

    1. 如果类或者资源是在包java.*中,那么交由父级类加载器代理完成,否则,搜索过程进入第二步。如果父类级类加载器加载失败,那么查找过程结束,加载失败。
    2. 如果类或者资源在启动代理序列(org.osgi.framework.bootdelegation)中定义,那么交由父级代理完成,此时的父级代理有启动参数org.osgi.framework.bundle.parent指定,默认是引导类加载器(bootstrap class loader),如果找到了类或者资源,那么查找过程结束。
    3. 如果类或者资源所在的包是在Import-Package中指定的,或者是在此之前通过动态导入加载的了,那么将请求转发到导出bundle的类加载器,否则搜索继续进行下一步;如果该包在启动参数org.osgi.framework.system.packages.extra中,则将请求转发给osgi容器外部的类加载器(通常是系统类加载器)。如果将请求交由导出类加载器代理,而类或者资源又没有找到,那么查找过程中止,同时请求失败。
    4. 如果包中类或者和资源所在的包由其他bundle通过是使用Require-Bundle从一个或多个其他bundle进行导入的了,那么请求交由其他那些bundle的类加载器完成,按照根据在bundle的manifest中指定的顺序进行查找进行查找。如果没有找到类或者资源,搜索继续进行。
    5. 使用bundle本身的内部bundle类路径查找完毕之后,。如果类或者资源还没有找到,搜索继续到下一步。
    6. 查找每一个附加的fragment的内部类路径,fragment的查找根据bundle ID顺序升序查找。如果没有找到类或者资源的,查找过程继续下一步。
    7. 如果包中类或者资源所在的包由bundle导出,或者包由bundle导入(使用Import-Package或者Require-Bundle),查找结束,即类或者资源没有找到。
    8. 否则,如果类或者资源所在的包是通过使用DynamicImport-Package进行导入,那么试图进行包的动态导入。导出者exporter必须符合包约束。如果找到了合适的导出者exporter,然后建立连接,以后的包导入就可以通过步骤三进行。如果连接建立失败,那么请求失败。
    9. 如果动态导入建立了,请求交由导出bundle的类加载器代理。如果代理查找失败,那么查找过程中止,请求失败。

    下面的非标准流程图展示了查找的过程:

    从规范可见,类加载的优先级顺序基本按照如下的原则:父容器classloader(通常是app classloader) –> 其他bundle的classloader –> 当前bundle的classloader –> 动态导入的包所在bundle的classloader。这个原则既可以使相同的类(包名也相同)尽可能只被加载一次,减少虚拟机perm区大小,也正因为如此,不同bundle中的相同的类,委托给同一个classloader加载,才能做到他们的对象和引用可以相互转换。(要知道一个类如果由不同的classloader加载后,其中一个classloader加载的类的对象是不能赋值给另一个classloader加载的类的引用的。)

    osgi类加载顺序的源码解析

    目前最流行的osgi框架实现有两个:equinoxfelix,下面分别列出equinox和felix在类加载顺序上的实现代码,如果大家在开发过程中发现一些类加载方面的奇怪现象,就可以通过找到如下的代码位置进行调试,从而跟踪出问题的根源,尤其是需要osgi容器内外共享类的时候。

    equinox的osgi类加载顺序实现代码

    在org.eclipse.osgi.internal.loader.BundleLoader.java的findClass(String name, boolean checkParent)和findClassInternal(String name, boolean checkParent, ClassLoader parentCL)方法中:

    Class<!--?--> findClass(String name, boolean checkParent) throws ClassNotFoundException {
    	ClassLoader parentCL = getParentClassLoader();
    	if (checkParent && parentCL != null && name.startsWith(JAVA_PACKAGE))
    		// 1) 如果类的包名以"java."开头,则委托给父容器加载器加载(默认是bootstrap classloader)
    		return parentCL.loadClass(name);
    	return findClassInternal(name, checkParent, parentCL);
    }
    private Class<!--?--> findClassInternal(String name, boolean checkParent, ClassLoader parentCL) throws ClassNotFoundException {
    	if (Debug.DEBUG_LOADER)
    		Debug.println("BundleLoader[" + this + "].loadBundleClass(" + name + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
    	String pkgName = getPackageName(name);
    	boolean bootDelegation = false;
    	// follow the OSGi delegation model
    	if (checkParent && parentCL != null && bundle.getFramework().isBootDelegationPackage(pkgName))
    		// 2) 如果该类在父容器类加载器委托清单中(即osgi容器启动参数org.osgi.framework.bootdelegation的值中)
    		// 则委托给父容器类加载器加载(默认是bootstrap classloader)。
    		try {
    			return parentCL.loadClass(name);
    		} catch (ClassNotFoundException cnfe) {
    			// we want to continue
    			bootDelegation = true;
    		}
    	Class<!--?--> result = null;
    	try {
    		result = (Class<!--?-->) searchHooks(name, PRE_CLASS);
    	} catch (ClassNotFoundException e) {
    		throw e;
    	} catch (FileNotFoundException e) {
    		// will not happen
    	}
    	if (result != null)
    		return result;
    	// 3) 在Import-Package中查找该类来自哪个bundle
    	PackageSource source = findImportedSource(pkgName, null);
    	if (source != null) {
    		// 3) 由查找到的bundle加载这个类
    		result = source.loadClass(name);
    		if (result != null)
    			return result;
    		throw new ClassNotFoundException(name);
    	}
    	// 4) 在Require-Bundle中查找哪一个bundle中有这个类
    	source = findRequiredSource(pkgName, null);
    	if (source != null)
    		// 4) attempt to load from source but continue on failure
    		result = source.loadClass(name);
    	// 5) 在当前的bundle中查找和加载该类
    	if (result == null)
    		result = findLocalClass(name);
    	if (result != null)
    		return result;
    	// 6) 在DynamicImport-Package中查找并加载
    	if (source == null) {
    		source = findDynamicSource(pkgName);
    		if (source != null) {
    			result = source.loadClass(name);
    			if (result != null)
    				return result;
    			// must throw CNFE if dynamic import source does not have the class
    			throw new ClassNotFoundException(name);
    		}
    	}
    	if (result == null)
    		try {
    			result = (Class<!--?-->) searchHooks(name, POST_CLASS);
    		} catch (ClassNotFoundException e) {
    			throw e;
    		} catch (FileNotFoundException e) {
    			// will not happen
    		}
    	// do buddy policy loading
    	if (result == null && policy != null)
    		result = policy.doBuddyClassLoading(name);
    	if (result != null)
    		return result;
    	// hack to support backwards compatibiility for bootdelegation
    	// or last resort; do class context trick to work around VM bugs
    	if (parentCL != null && !bootDelegation && ((checkParent && bundle.getFramework().compatibiltyBootDelegation) || isRequestFromVM()))
    		// we don't need to continue if a CNFE is thrown here.
    		try {
    			return parentCL.loadClass(name);
    		} catch (ClassNotFoundException e) {
    			// we want to generate our own exception below
    		}
    	throw new ClassNotFoundException(name);
    }

    felix的osgi类加载顺序实现代码

    在org.apache.felix.framework.BundleWiringImpl.java类的findClassOrResourceByDelegation(String name, boolean isClass) 方法中:

    private Object findClassOrResourceByDelegation(String name, boolean isClass)
        throws ClassNotFoundException, ResourceNotFoundException
    {
        Object result = null;
     
        Set requestSet = (Set) m_cycleCheck.get();
        if (requestSet == null)
        {
            requestSet = new HashSet();
            m_cycleCheck.set(requestSet);
        }
        if (requestSet.add(name))
        {
            try
            {
                // Get the package of the target class/resource.
                String pkgName = (isClass)
                    ? Util.getClassPackage(name)
                    : Util.getResourcePackage(name);
     
                // 如果该类在父容器类加载器委托清单中(即osgi容器启动参数org.osgi.framework.bootdelegation的值中)
                if (shouldBootDelegate(pkgName))
                {
                    try
                    {
                        // 根据osgi容器启动参数org.osgi.framework.bundle.parent的值来获得适当的父类加载器
                        // 默认是bootstrap classloader
                        ClassLoader bdcl = getBootDelegationClassLoader();
                        result = (isClass)
                            ? (Object) bdcl.loadClass(name)
                            : (Object) bdcl.getResource(name);
                        // If this is a java.* package, then always terminate the
                        // search; otherwise, continue to look locally if not found.
                        if (pkgName.startsWith("java.") || (result != null))
                        {
                            return result;
                        }
                    }
                    catch (ClassNotFoundException ex)
                    {
                        // If this is a java.* package, then always terminate the
                        // search; otherwise, continue to look locally if not found.
                        if (pkgName.startsWith("java."))
                        {
                            throw ex;
                        }
                    }
                }
     
                // 在Import-Package和Require-Bundle中查找是否有该类,有则委托给相应的加载器加载,详见下面searchImports方法的代码
                result = searchImports(pkgName, name, isClass);
     
                // 如果还没找到,就在当前bundle的classpath下查找是否有该类,有则加载.
                if (result == null)
                {
                    if (isClass)
                    {
                        ClassLoader cl = getClassLoaderInternal();
                        if (cl == null)
                        {
                            throw new ClassNotFoundException(
                                "Unable to load class '"
                                + name
                                + "' because the bundle wiring for "
                                + m_revision.getSymbolicName()
                                + " is no longer valid.");
                        }
                        result = (Object) ((BundleClassLoader) cl).findClass(name);
                    }
                    else
                    {
                        result = (Object) m_revision.getResourceLocal(name);
                    }
     
                    // 如果还是没找到,则在DynamicImport-Package中查找和加载
                    if (result == null)
                    {
                        result = searchDynamicImports(pkgName, name, isClass);
                    }
                }
            }
            finally
            {
                requestSet.remove(name);
            }
        }
        else
        {
            // If a cycle is detected, we should return null to break the
            // cycle. This should only ever be return to internal class
            // loading code and not to the actual instigator of the class load.
            return null;
        }
     
        if (result == null)
        {
            if (isClass)
            {
                throw new ClassNotFoundException(
                    name + " not found by " + this.getBundle());
            }
            else
            {
                throw new ResourceNotFoundException(
                    name + " not found by " + this.getBundle());
            }
        }
     
        return result;
    }
     
    // 在Import-Package和Require-Bundle中查找是否有该类,有则委托给相应的加载器加载
    private Object searchImports(String pkgName, String name, boolean isClass)
    	throws ClassNotFoundException, ResourceNotFoundException
    {
    	// 从Import-Package中查找和加载.
    	BundleRevision provider = m_importedPkgs.get(pkgName);
    	if (provider != null)
    	{
    		// 如果pkgName配置在启动参数org.osgi.framework.system.packages.extra 的值中,
    		// 则由启动osgi容器的类加载器来加载这个类或者资源,(一般情况是系统类加载器加载)。
    		// 其classloader来自ExtensionManagerWiring的getClassByDelegation 方法,
    		// 其实质是ExtensionManagerWiring的getClass().getClassLoader().loadClass(name) 来加载的,
    		// 而ExtensionManagerWiring类是由启动osgi容器的类加载器加载;
    		// 如果pkgName未配置在启动参数org.osgi.framework.system.packages.extra的清单中,
    		// 则用相应的bundle的classloader来加载
    		Object result = (isClass)
    			? (Object) ((BundleWiringImpl) provider.getWiring()).getClassByDelegation(name)
    			: (Object) ((BundleWiringImpl) provider.getWiring()).getResourceByDelegation(name);
    		if (result != null)
    		{
    			return result;
    		}
    		// If no class or resource was found, then we must throw an exception
    		// since the provider of this package did not contain the
    		// requested class and imported packages are atomic.
    		if (isClass)
    		{
    			throw new ClassNotFoundException(name);
    		}
    		throw new ResourceNotFoundException(name);
    	}
    	// 从Require-Bundle中查找和加载.
    	List providers = m_requiredPkgs.get(pkgName);
    	if (providers != null)
    	{
    		for (BundleRevision p : providers)
    		{
    			// If we find the class or resource, then return it.
    			try
    			{
    				Object result = (isClass)
    					? (Object) ((BundleWiringImpl) p.getWiring()).getClassByDelegation(name)
    					: (Object) ((BundleWiringImpl) p.getWiring()).getResourceByDelegation(name);
    				if (result != null)
    				{
    					return result;
    				}
    			}
    			catch (ClassNotFoundException ex)
    			{
    				// Since required packages can be split, don't throw an
    				// exception here if it is not found. Instead, we'll just
    				// continue searching other required bundles and the
    				// revision's local content.
    			}
    		}
    	}
    	return null;
    }

     

    展开全文
  • 这回来分析一下OSGI类加载机制。 先说一下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类的注册方法。

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

     

    转载于:https://www.cnblogs.com/cz123/p/6918708.html

    展开全文
  • OSGi类加载问题

    2015-07-01 23:58:00
    ——OSGi找不到路径问题。 ——线程死锁问题。 问题一:OSGi类内存问题 其次,从内存用量来看,OSGi允许不同版本的Package同时存在,这是个优点,但是客观上会占用更多内存。例如,一个库可能需要 ASM 3.0,...

    项目中遇到的JVM难点

    ——启动OSGi容器时,出现永久代内存不够。 内存泄露
    ——OSGi找不到类路径问题。
    —— 线程死锁问题。
     

    问题一:OSGi类内存问题

            其次,从内存用量来看,OSGi允许不同版本的Package同时存在,这是个优点,但是客观上会占用更多内存。例如,一个库可能需要 ASM 3.0,而同一应用程序使用的另一个库可能需要ASM 2.0,要解决这种问题,通常需要更改代码,而在OSGi中只需要付出一点Java 方法区的内存即可解决。
     
            不过,如果对OSGi动态性使用不当,可能会因为 不正确持有某个过期模块(被更新或卸载的模块)中一个类的实例,导致该类的类加载器无法被回收,进而导致该类加载器下所有类都无法被GC回收掉。
     
            为了防止保留类加载器带来的内存泄露,我们必须使用弱键和弱值。目标是不在内存中保持一个已卸载的bundle的类空间。我们必须使用弱值,因为每 个映射项目的值(BridgeClassLoader)都强引用着键(ClassLoader),于是以此方式否定它的“弱点”。这是WeakHashMap javadoc规定的标准建议。通过使用一个弱缓存我们避免了跟踪所有的bundle,而且不必对他们的生命周期做出反应。

    问题二:运行时的ClassNotFoundException

    情形1: 间接引用带来的问题
    原因:Plugin_1 没有直接使用Plugin_2的Java文件,但运行时使用了Plugin_2中的Jar包中的文件。
    解决措施:将Plugin_2中的Jar包导出来, 同时在Plugin_1中导入这些包。
     
    情形2: 利用String反射到类或对象的时候出现的问题
    Plugin_1涉及到由String转化为类对象Plugin_2_ClassA(Plugin_2_ClassA表示Plugin_2中的Class  A)的操作, 这个时候调用非常隐晦, 也需要按照情形1进行处理。
     
    情形3:  加载顺序不对带来的问题
    IDE环境下, 这个问题出现在控制台中
    IDE环境下, 这个问题出现在configuration目录下面的Log目录中。
     
    1. 如果是IDE环境下出现的问题,   Run Configuration中配置一下启动顺序
    2. 发布环境下, config.ini 需要做新的调整,先加载底层插件,再加载依赖插件,最后加载不被任何插件依赖的项目 (参见文章开始的参考资料4)
    3. 发布环境下, 如果问题一再出现, 请删除\configuration目录下的org.eclipse.osgi 目录, 再进行启动
     
     

    OSGi类加载流程

    OSGi每个模块都有自己独立的classpath。
    如何实现这一点呢?是因为OSGi采取了不同的类加载机制:
    ——OSGi为每个bundle提供一个类加载器,该加载器能够看到bundle Jar文件内部的类和资源;
    ——为了让bundle能互相协作,可以基于依赖关系,从一个bundle类加载器委托到另一个bundle类加载器。
     
    Java和J2EE的类加载模型都是层次化的,只能委托给上一层类加载器;
     
    而OSGi类加载模型则是网络图状的,可以在bundle间互相委托。——这样更合理,因为bundle间的依赖关系并不是层次化的。
     
     

    优点

    找不到类时的错误提示更友好。假如bundleE不存在,则bundleC就不会被解析成功,会有错误消息提示为何未能解析;而不是报错ClassNotFoundException或NoClassDefFoundError。
    效率更高。
     
    在标准Java类加载模型中,总是会在classpath那一长串列表中进行查找;而OSGi类加载器能立即知道去哪里找类。
     

    流程

    Step 1: 检查是否java.*,或者在bootdelegation中定义
    当bundle类加载器需要加载一个类时,首先检查包名是否以java.*开头,或者是否在一个特定的配置文件(org.osgi.framework.bootdelegation)中定义。如果是,则bundle类加载器立即委托给父类加载器(通常是Application类加载器)。
     
    这么做有两个原因:
    唯一能够定义java.*包的类加载器是bootstrap类加载器,这个规则是JVM要求的。如果OSGI bundle类加载器试图加载这种类,则会抛Security Exception。
    一些JVM错误地假设父加载器委托永远会发生,内部VM类就能够通过任何类加载器找到特定的其他内部类。所以OSGi提供了org.osgi.framework.bootdelegation属性,允许对特定的包(即那些内部VM类)使用父加载器委托。
     
    Step 2: 检查是否在Import-Package中声明
    检查是否在Import-Package中声明。如果是,则找到导出包的bundle,将类加载请求委托给该bundle的类加载器。如此往复。
     
    Step 3: 检查是否在Require-Bundle中声明
    检查是否在Require-Bundle中声明。如果是,则将类加载请求委托给required bundle的类加载器。
     
    Step 4: 检查是否bundle内部类
    检查是否是该bundle内部的类,即当前JAR文件中的类。
     
    Step5:  检查fragment
    搜索可能附加在当前bundle上的fragment中的内部类。
     
    什么是fragment?
    Fragment bundle是OSGi 4引入的概念,它是一种不完整的bundle,必须要附加到一个host bundle上才能工作;fragment能够为host bundle添加类或资源,在运行时,fragment中的类会合并到host bundle的内部classpath中。
     
    fragment有什么作用?
     
    【场景1】bundle中有针对特定平台的代码
    假设bundle对不同平台的实现方式稍有不同,Windows和Linux下代码有不同之处,即bundle中有针对特定平台的代码。
    我们应该为每个平台提供不同的bundle吗?——显然不能,因为那会造成代码重复。
     
    或者将共同代码放到bundle A中,Windows特定的那部分代码放到bundle Pwin中,Linux特定的那部分代码放到bundle Plinux中。——有问题:Pwin肯定要依赖A中某些包,我们就必须在A中导出这些包,如果只有Pwin用到这些包 岂不破坏封装性。
     
    最好的解决方法是把Pwin作为fragment,附加到A中。这样Pwin就能看到A中的所有包,A也能看到Pwin的所有包。
     
    【场景2】针对不同国家用户提供不同的i18n
     
    GUI程序通常会通过properties文件定义i18n信息,可以将不同的i18n存到不同的fragment中。运行时用户只需要下载host bundle以及特定的i18n fragment即可,不需要把其他国家的i18n也下载下来。
     
     
    Step6: 动态类加载
     

    OSGi:灵活的类加载器架构

    P279
    高并发环境下死锁问题
    都会锁定自己的类加载器实例。
     

    转载于:https://www.cnblogs.com/lsx1993/p/4614904.html

    展开全文
  • 一、OSGi 规范 OSGi(Open Service Gateway Initiative) 技术是 Java 动态化模块化系统的一系列规范。OSGi 一方面指维护 OSGi 规范的 OSGi Alliance(OSGi 联盟),另一方面指的是该组织维护的基于 Java 语言的服务...

     

    一、OSGi 规范

    OSGi(Open Service Gateway Initiative) 技术是 Java 动态化模块化系统的一系列规范。OSGi 一方面指维护 OSGi 规范的 OSGi Alliance(OSGi 联盟),另一方面指的是该组织维护的基于 Java 语言的服务(业务)规范。简单来说,OSGi 可以认为是 Java 平台的模块层,为大型分布式系统以及嵌入式系统提供一种模块化架构减少了软件的复杂度。

    1. 什么是 OSGi

    OSGi 联盟(OSGi Alliance)于 1999 年开始着手制定 OSGi 规范,其主要目的就是要制定一套开放式标准,以便向局域网及其中的设备提供可管理的服务;其基本思路是,一旦您在网络设备(如服务器和嵌入式设备)上使用了 OSGi 服务平台,您就可以在网络上的任何地方管理这些设备上运行的软件组件的生命周期,可以在后台对这些组件进行安装、升级或卸载,但不需要打断该设备的正常运行。Eclipse 就是基于 OSGi 开发的。

    2. OSGi 标准

    OSGI R1 于 2000 年发布,现在最新的标准版本是 R5,到现在为止应用最广泛的当属是 2005 年发布的 R4。

    其中主要定义了 OSGi 框架。OSGi 框架提供了一个通用安全可管理的 java 框架,能够支持可扩展可下载的应用(即 bundles)的部署。OSGi 框架是 OSGi 技术最基础也是最核心的部分。

    这里你需要理解 OSGi 框架的三个最重要部分:模块层、生命周期层、服务层。

    图1.1 OSGi 框架组成

    • 模块层 :关注打包和代码共享。

      OSGi 是严格要求模块化的,模块有个专有名词 bundle。每个模块都是一个 bundle,一个 Business Logic 由多个 bundle 来实现。

      注:后面全部使用 bundle 代替模块来表述。

    • 生命周期层 :关注提供执行模块管理和对底层 OSGi 框架的访问。

      bundle 是需要 OSGi 进行解析的,每个 bundle 在变得可用之前,都需要完整经历该生命周期。

    • 服务层 :关注模块,特别是模块内的组件的交互和通讯。

      OSGi 技术全面贯彻了 SOA,每个 bundle 都是其他 bundle 提供服务,夸张一点说,不提供服务的 bundle 就没有存在的价值。

    2.1 OSGi 三层架构-模块层

    模块化其实就是计算机科学中常见的一个概念:将一个大型系统分解为多个较小的互相协作的逻单元,通过强制设定模块之间的逻辑边界来改善系统的维护性和封装性。

    模块层定义了 OSGi 中的模块 bundle:

    • bundle 是以 jar 包形式存在的个模块化物理单元,里面包含了代码,资源文件和元数据(metadata),井且 jar 包的物理边界也同时是运行时逻辑模块的封装边界。

    • bundle 是开发、部署 OSGi 应用的基本单元。

    • bundle 的核心是 META-NF 目录下的 MANIFEST.MF 文件。

    • bundle 定义了其所包含的包的可见性、可以认为是在 public/private/protected 的基础上的一个扩展。

    • bundle 的 java 包共享、屏蔽的规则。通过 Export-Package、Import-Package 方式进行交互。

    • 每个 bundle 都有单独的类加加载器。

    来看看在 MANIFEST.MF 中可以定义哪些 Bundle 的的元数据信息:

    属性属性描述
    Bundle-ActivatorBundle 的 Activator 类名
    Bundle-CategoryBundle 的分类属性描述
    Bundle-ClasspathBundle 的 Classpath
    Bundle-CopyrightBundle 的版权
    Bundle-DescriptionBundle 的描述信息
    Bundle-DocURLBundle 的文档 URL 地址
    Bundle-LocalizationBundle 的国际化文件
    Bundle-ManifestVersion定义 Bundle 所遵循的规范的版本, OSGI R3 对应的值为1, OSGI R4 对应的值为2
    Bundle-NameBundle的有意义的名称
    Bundle-NativeCodeBundle 所引用的 Native Code 的地址
    bundle-RequiredExecutionEnvironmentBundle 运行所需要的环境,如可指定为需要 OSGI R3、Java1.4、Java1.3 等
    Bundle-SymbolicNameBundle 的唯一标识名,可采用类似 java package 名的机制来保证唯一性
    Bundle-VersionBundle 的版本
    Dynamiclmport-PackageBundle 动态引用的 package
    Export-PackageBundle对外暴露的 package
    Fragment-HostFragment 类型 Bundle 所属的 Bundle 名
    Import-PackageBundle 引用的 package
    Require-BundleBundle 所需要引用的其他的 Bundle

    2.2 OSGi 三层架构-生命周期

    状态是 Bundle 在运行期的一项动态属性,不同状态的 Bundle 具有不同的行为,生命周期层规范定义了 Bundle 生命周期过程之中的 6 种状态。

    图1.2 Bundle生命周期的6种状态

    OSGi 生命周期层有两种不同的作用:

    • 在应用程序外部,定义了对 bundle 生命周期的相关操作。OSGi 生命周期层允许在执行时,从外部安装、启动、更新、停止、卸载不同的 bundle 进而定制应用的配置。

    • 在应用程序内部,定义了 bundle 访问其执行上下文的方式,为 bundle 提供了一种与 OSGi 框架交互的途径以及一些执行时的便利条件。

    2.3 OSGi 三层架构-服务层

    OSGi 的服务层除了面向服务的编程模型,还有一个区别于其他很多类似模型的特性。也就是说,当一个 bundle 发现并开始使用 OSGi 中的一个服务了以后,这个服务可能在任何的时候改变或者是消失。

    OSGi 框架有一个中心化的注册表,这个注册表从 publish-find-bind 模型:

    图1.3 OSGi服务层

    3. OSGi 纲要规范

    除了上述 OSGi 核心规范(core specification)中的服务,OSGi 联盟也定义了一组非核心的(non-core)标准服务,称为 compendium 服务。Core 服务在任何运行的 OSGi 框架内都是可用的,这要求所有的 OSGi 框架都要实现核心服务。而 compendium 服务则不然。这些服务以分离的 bundle 的形式出现,由框架实施者或者第三方实现并提供,能在任何框架上运行。

    • LOG Service(日志服务)
    • HTTP Service(注册 servlet 和资源)
    • Configuration Admin(配置管理)
    • Event Admin(事件通知)
    • Declarative Services(定义轻量级的面向服务的组件模型)
    • Blueprint(一个类似 IOC 容器的实现)

    4. OSGi 特点

    从开发的角度:

    • 复杂性的降低:基于 OSGi 的组件横型 bundle 能够隐藏内部实现,bundle 基于服务进行交互。
    • 复用:很多第三方的组件可以以 bundle 的形式进行复用。
    • 简单:核心的 API 总过包括不超过 30 个类和接口。
    • 小巧: OSGi R4 和实现仅需要 300KB 的 JAR file 就足够。在系统中引入 OSGi 几乎有什么开销。
    • 非侵入式:服务可以以 POJO 的形式实现,不需要关注特定的接口。

    从可维护的角度:

    • 切合真实运行环境:OSGi 框架是动态的,bundle 能够进行即时的更新,服务可以根据需要动态增加或者删除。
    • 易于部署:OSGi 定义了组件是如何安装和管理的,标准化的管理 API 使得 OSGi。 能够和现有和将来的各种系统有机的集成。
    • 动态更新:这是 OSGi 被最经常提起的一个特性,即所谓的 "热插拔" 特性,bundle 能够动态的安装、启动、停止、更新和卸载,而整个系统无需重启。
    • 适配性:这主要得益于 OSGi 提供的服务机制、组件可以动态的注册、获取和监听服务,使得系统能够在 OSGi 环境调整自己的功能。
    • 版本化:bundle 可以版本化,多版本能够共存而不会影响系统功能。
    • 懒加载:OSGi 技术采用了很多懒加载机制。比如服务可以被注册,但是直到被使用时才创建。

    5. OSGi VS 传统 java 模块化

    "OSGi" 相比 "传统 java 模块化" 有以下优势:

    • 基于接口编程,完全隐藏底层代码实现
    • 动态性(对扩展开放,即使是运行时的)
    • 版本控制

    部分支持 OSGi 的项目:

    Apache cxf、Apache thrift、Apache activemq、Apache curator Activiti、drools、mysql-connector-java、postgresql(java client)、rabbitmq(java client)、hazelcast

    OSGi 的缺点:

    • 目前只支持Java
    • 入门高、技术更加复杂
    • 相对的重量级
    • 资料匮乏
    • lib 支持的不好,很多 lib 无法在 OSGi 环境下运行

    6. OSGi 开源框架介绍

    1. Equinox:OSGi R4 core framework 的一个实现,一组实现各种可选的 OSGi bundle 和一些开发基于 OSGi 技术的系统所需要的基础构件。 Eclipse 是基于 Equinox 项目开发的一个典型例子。具体内容可以从 http://www.eclipse.org/equinox/ 下载。

      比较适合不需要集成太多外部技术的应用,如桌面应用开发,当需要进行集成时,会遇到相当多的兼容性问题;

    2. Apache Felix:实现 OSGi R4 规范(包括 OSGi 框架,Standard Service 和其它 OSGi 相关技术)的另一个开源项目。具体内容可以从 http://felix.apache.org/ 下载。

      与 Equinox 非常相似,都属于基础环境。但也有一个最大的不同,其兼容性、可扩展性都比较强,能够很方便的集成 Web Container、DataSource 管理等许多实际开发中必须具备的组件。但是这里有个很大的隐患:所有的集成都需要手工完成,质量、测试都无法保证,作为系统最重要的基础运行环境,其稳定性、可靠性是至关重要的。

    3. Apache Karaf:一个基于 OSGi 的运行环境,它提供了一个轻量级的 OSGi 容器,可以用于部署各种组件和应用程序。它提供了很多的组件和功能用于帮助开发人员更加灵活的部署应用,更适合作为商业化产品的开发、运行平台。具体内容可以从 http://karaf.apache.org/ 下载。

    结论:使用 karaf 作为接下来的 OSGi 基础平台,进行下一步的 JavaEE 与 OSGi 整合工作。

     

    二、 深入理解OSGi类加载机制

    OSGi之所以能够实现模块热插拔和模块内部可见性的精准控制都归结于其特殊的类加载机制.加载器之间的关系不再是双亲委派模型的树状结构,而是发展成复杂的网状结构。

           根据OSGi规范说明:

    对于类或资源加载,框架必须遵循以下规则。 当请求bundle的类加载器加载类或资源时,必须按以下顺序执行搜索:

    1. 如果类或资源在java.*包中,则将请求委托给父类加载器; 否则,继续下一步搜索。 如果请求被委托给父类加载器还找不到类或资源,则搜索终止并且失败。
    2. 如果类或资源来自引导委派列表(系统变量org.osgi.framework.bootdelegation)中包含的包,则将请求委托给父类加载器。如果在那里找不到类或资源,继续下一步搜索。
    3. 如果类或资源属于声明在Import-Package导入的包中,或者是在先前的加载中动态导入的,那么请求将委托给声明Export-Package这个包的bundle的类加载器;否则继续下一步搜索。如果请求被委托给导出类加载器但找不到类或资源,则失败。
    4. 如果类或资源位于在多个Require-Bundle包中导入的包中,则请求将按照清单中指定的顺序委派给其他包的类加载器。这个过程中使用深度优先策略;如果未找到类或资源,则继续下一步搜索。
    5. 搜索bundle的内嵌jar的类路径(Bundle Class Path)。如果找不到类或资源,继续下一步。
    6. 查找Bundle的Fragment Bundle中导入的包, 如果没找到继续下一步
    7. 如果类或资源位于自己导出的包中,则搜索结束并失败。
    8. 否则,如果类或资源位于DynamicImport-Package导入的包中,则尝试动态导入包。
    9. 如果动态导入包成功,则将请求委托给导出这个包的类加载器。如果请求被委托给导出类加载器并且找不到类或资源,则搜索终止且失败。

    总体图:  

         但这种模式也会产生许多隐患,比如循环依赖问题,如果BundleA依赖BundleB , BundleB依赖BundleC, BundleC又依赖BundleA, 这可能在加载Bundle的时候导致死锁问题。为了避免这种情况,根据OSGi规范说明,在这种情况下,框架必须在第一次访问Bundle的时候做标记,不去访问已经访问过的Bundle.

         另外,在OSGi中Bundle都有自己独有的ClassLoader, Fragment Bundle不同于普通Bundle, 其和其附着的Host Bundle共享一个ClassLoader.


    三、Java默认类加载器机制和OSGI类加载器机制

     

    Java默认类加载机制介绍

    类加载器

      

          虚拟机设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块称为“类加载器”。

          类加载器可以说是Java语言的一项创新,也是Java语言流行的重要原因之一,它最初是为了满足Java Applet的需求而开发出来的。虽然目前Java Applet技术基本上已经“死掉”,但类加载器却在类层次划分、OSGi、热部署、代码加密等领域大放异彩,成为了Java技术体系中一块重要的基石,可谓是失之桑榆,收之东隅。

          类加载器虽然只用于实现类的加载动作,但它在Java程序中起到的作用却远远不限于类加载阶段。对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性,每一个类,都拥有一个独立的类名称空间。这句话可以表达得更通俗一些:比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义。否则,即使这两个类来源于同一个Class文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等。

     

    双亲委派模型

     

     

          从Java虚拟机的角度来讲,只存在两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),这个类加载器使用C++语言实现,是虚拟机自身的一部分;另一种就是所有其他的类加载器,这些类加载器都由Java语言实现,独立于虚拟机外部,并且全都继承自抽象类java.lang.ClassLoader。

          从Java开发人员的角度来看, 类加载器还可以划分得更细致一些, 绝大部分Java程序都会使用到以下3种系统提供的类加载器。

    1)启动类加载器(Bootstrap ClassLoader):前面已经介绍过,这个类加载器负责将存放在<JAVA_HOME>\lib目录中的,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如rt.jar,名字不符合的类库即使放在lib目录中也不会被加载)类库加载到虚拟机内存中。启动类加载器无法被Java程序直接引用。

    2)扩展类加载器(Extension ClassLoader):这个加载器由sun.misc.Launcher.ExtClassLoader实现,它负责加载<JAVA_HOME>\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器。

    3)应用程序类加载器(Application ClassLoader):这个类加载器由sun.misc.Launcher.AppClassLoader实现。由于这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也称它为系统类加载器。它负责加载用户类路径(Class Path)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

          我们的应用程序都是由这3种类加载器互相配合进行加载的,如果有必要,还可以加入自己定义的类加载器。这些类加载器之间的关系一般如下图所示。

     

          图中展示的类加载器之间的这种层次关系,称为类加载器的双亲委派模型(Parents Delegation Model)。双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器。这里类加载器之间的父子关系一般不会以继承(Inheritance)的关系来实现,而是都使用组合(Composition)关系来复用父加载器的代码。类加载器的双亲委派模型在JDK 1.2期间被引入并被广泛应用于之后几乎所有的Java程序中,但它并不是一个强制性的约束模型,而是Java设计者推荐给开发者的一种类加载器实现方式。

          双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。

          使用双亲委派模型来组织类加载器之间的关系,有一个显而易见的好处就是Java类随着它的类加载器一起具备了一种带有优先级的层次关系。例如类java.lang.Object,它存放在rt.jar之中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。相反,如果没有使用双亲委派模型,由各个类加载器自行去加载的话,如果用户自己编写了一个称为java.lang.Object的类,并放在程序的Class Path中,那系统中将会出现多个不同的Object类,Java类型体系中最基础的行为也就无法保证,应用程序也将会变得一片混乱。如果读者有兴趣的话,可以尝试去编写一个与rt.jar类库中已有类重名的Java类,将会发现可以正常编译,但永远无法被加载运行。

          双亲委派模型对于保证Java程序的稳定运作很重要,但它的实现却非常简单,实现双亲委派的代码都集中在java.lang.ClassLoader的loadClass()方法之中,如以下代码所示,逻辑清晰易懂:

     

    [java] view plain copy

    1. protected synchronized Class<?> loadClass(String name, boolean resolve)  
    2. throws ClassNotFoundException  
    3. {  
    4.     //首先, 检查请求的类是否已经被加载过了  
    5.     Class c=findLoadedClass(name);  
    6.     if( c== null ){  
    7.         try{  
    8.             if( parent != null ){  
    9.                 c = parent.loadClass(name,false);  
    10.             } else {  
    11.                 c = findBootstrapClassOrNull(name);  
    12.             }  
    13.         } catch (ClassNotFoundException e) {  
    14.         //如果父类加载器抛出ClassNotFoundException  
    15.         //说明父类加载器无法完成加载请求  
    16.         }  
    17.         if( c == null ) {  
    18.             //在父类加载器无法加载的时候  
    19.             //再调用本身的findClass方法来进行类加载  
    20.             c = findClass(name);  
    21.         }  
    22.     }   
    23.     if(resolve){  
    24.         resolveClass(c);  
    25.     }  
    26.     return c;  
    27. }  

          先检查是否已经被加载过,若没有加载则调用父加载器的loadClass()方法,若父加载器为空则默认使用启动类加载器作为父加载器。如果父类加载失败,抛出ClassNotFoundException异常后,再调用自己的findClass()方法进行加载。双亲委派的具体逻辑就实现在这个loadClass()方法之中,JDK 1.2之后已不提倡用户再去覆盖loadClass()方法,而应当把自己的类加载逻辑写到findClass()方法中,在loadClass()方法的逻辑里如果父类加载失败,则会调用自己的findClass()方法来完成加载,这样就可以保证新写出来的类加载器是符合双亲委派规则的。

     

    打破双亲委派模型

     

          上文提到过双亲委派模型并不是一个强制性的约束模型,而是Java设计者推荐给开发者的类加载器实现方式。在Java的世界中大部分的类加载器都遵循这个模型,但也有例外。

          双亲委派模型的一次“被破坏”是由这个模型自身的缺陷所导致的,双亲委派很好地解决了各个类加载器的基础类的统一问题(越基础的类由越上层的加载器进行加载),基础类之所以称为“基础”,是因为它们总是作为被用户代码调用的API,但世事往往没有绝对的完美,如果基础类又要调用回用户的代码,那该怎么办?这并非是不可能的事情,一个典型的例子便是JNDI服务,JNDI现在已经是Java的标准服务,它的代码由启动类加载器去加载(在JDK 1.3时放进去的rt.jar),但JNDI的目的就是对资源进行集中管理和查找,它需要调用由独立厂商实现并部署在应用程序的Class Path下的JNDI接口提供者(SPI,Service Provider Interface)的代码,但启动类加载器不可能“认识”这些代码,因为启动类加载器的搜索范围中找不到用户应用程序类,那该怎么办?为了解决这个问题,Java设计团队只好引入了一个不太优雅的设计:线程上下文类加载器(Thread Context ClassLoader)。这个类加载器可以通过java.lang.Thread类的setContextClassLoader()方法进行设置,如果创建线程时还未设置,它将会从父线程中继承一个,如果在应用程序的全局范围内都没有设置过的话,那这个类加载器默认就是应用程序类加载器(Application ClassLoader)。

          有了线程上下文类加载器,就可以做一些“舞弊”的事情了,JNDI服务使用这个线程上下文类加载器去加载所需要的SPI代码,也就是父类加载器请求子类加载器去完成类加载的动作,这种行为实际上就是打通了双亲委派模型的层次结构来逆向使用类加载器,实际上已经违背了双亲委派模型的一般性原则,但这也是无可奈何的事情。Java中所有涉及SPI的加载动作基本上都采用这种方式,例如JNDI、JDBC、JCE、JAXB和JBI等。

          双亲委派模型的另一次“被破坏”是由于用户对程序动态性的追求而导致的,这里所说的“动态性”指的是当前一些非常“热门”的名词:代码热替换(HotSwap)、模块热部署(HotDeployment)等,说白了就是希望应用程序能像我们的计算机外设那样,接上鼠标、U盘,不用重启机器就能立即使用,鼠标有问题或要升级就换个鼠标,不用停机也不用重启。对于个人计算机来说,重启一次其实没有什么大不了的,但对于一些生产系统来说,关机重启一次可能就要被列为生产事故,这种情况下热部署就对软件开发者,尤其是企业级软件开发者具有很大的吸引力。Sun公司所提出的JSR-294、JSR-277规范在与JCP组织的模块化规范之争中落败给JSR-291(即OSGi R4.2),虽然Sun不甘失去Java模块化的主导权,独立在发展Jigsaw项目,但目前OSGi已经成为了业界“事实上”的Java模块化标准,而OSGi实现模块化热部署的关键则是它自定义的类加载器机制的实现。每一个程序模块(OSGi中称为Bundle)都有一个自己的类加载器,当需要更换一个Bundle时,就把Bundle连同类加载器一起换掉以实现代码的热替换。

          在OSGi环境下,类加载器不再是双亲委派模型中的树状结构,而是进一步发展为更加复杂的网状结构,当收到类加载请求时,OSGi将按照下面的顺序进行类搜索:

    1)将以java.*开头的类委派给父类加载器加载。

    2)否则,将委派列表名单内的类委派给父类加载器加载。

    3)否则,将Import列表中的类委派给Export这个类的Bundle的类加载器加载。

    4)否则,查找当前Bundle的Class Path,使用自己的类加载器加载。

    5)否则,查找类是否在自己的Fragment Bundle中,如果在,则委派给Fragment Bundle的类加载器加载。

    6)否则,查找Dynamic Import列表的Bundle,委派给对应Bundle的类加载器加载。

    7)否则,类查找失败。

          上面的查找顺序中只有开头两点仍然符合双亲委派规则,其余的类查找都是在平级的类加载器中进行的。

          只要有足够意义和理由,突破已有的原则就可认为是一种创新。正如OSGi中的类加载器并不符合传统的双亲委派的类加载器,并且业界对其为了实现热部署而带来的额外的高复杂度还存在不少争议,但在Java程序员中基本有一个共识:OSGi中对类加载器的使用是很值得学习的,弄懂了OSGi的实现,就可以算是掌握了类加载器的精髓。


     

     

    OSGi:灵活的类加载器架构

      既然说到OSGI,就要来解释一下OSGi是什么,以及它的作用

      OSGi(Open Service Gateway Initiative):是OSGi联盟指定的一个基于Java语言的动态模块化规范,这个规范最初是由Sun、IBM、爱立信等公司联合发起,目的是使服务提供商通过住宅网管为各种家用智能设备提供各种服务,后来这个规范在Java的其他技术领域也有不错的发展,现在已经成为Java世界中的“事实上”的模块化标准,并且已经有了Equinox、Felix等成熟的实现。OSGi在Java程序员中最著名的应用案例就是Eclipse IDE

      OSGi中的每一个模块(称为Bundle)与普通的Java类库区别并不大,两者一般都以JAR格式进行封装,并且内部存储的都是Java Package和Class。但是一个Bundle可以声明它所依赖的Java Package(通过Import-Package描述),也可以声明他允许导出发布的Java Package(通过Export-Package描述)。在OSGi里面,Bundle之间的依赖关系从传统的上层模块依赖底层模块转变为平级模块之间的依赖(至少外观上如此),而且类库的可见性能得到精确的控制,一个模块里只有被Export过的Package才可能由外界访问,其他的Package和Class将会隐藏起来。除了更精确的模块划分和可见性控制外,引入OSGi的另外一个重要理由是,基于OSGi的程序很可能可以实现模块级的热插拔功能,当程序升级更新或调试除错时,可以只停用、重新安装然后启动程序的其中一部分,这对企业级程序开发来说是一个非常有诱惑性的特性

      OSGi之所以能有上述“诱人”的特点,要归功于它灵活的类加载器架构。OSGi的Bundle类加载器之间只有规则,没有固定的委派关系。例如,某个Bundle声明了一个它依赖的Package,如果有其他的Bundle声明发布了这个Package,那么所有对这个Package的类加载动作都会为派给发布他的Bundle类加载器去完成。不涉及某个具体的Package时,各个Bundle加载器是平级关系,只有具体使用某个Package和Class的时候,才会根据Package导入导出定义来构造Bundle间的委派和依赖

      另外,一个Bundle类加载器为其他Bundle提供服务时,会根据Export-Package列表严格控制访问范围。如果一个类存在于Bundle的类库中但是没有被Export,那么这个Bundle的类加载器能找到这个类,但不会提供给其他Bundle使用,而且OSGi平台也不会把其他Bundle的类加载请求分配给这个Bundle来处理

      一个例子:假设存在BundleA、BundleB、BundleC三个模块,并且这三个Bundle定义的依赖关系如下:

      BundleA:声明发布了packageA,依赖了java.*的包

      BundleB:声明依赖了packageA和packageC,同时也依赖了Java.*的包

      BundleC:声明发布了packageC,依赖了packageA

      那么,这三个Bundle之间的类加载器及父类加载器之间的关系如下图:

      

      由于没有涉及到具体的OSGi实现,所以上图中的类加载器没有指明具体的加载器实现,只是一个体现了加载器之间关系的概念模型,并且只是体现了OSGi中最简单的加载器委派关系。一般来说,在OSGi中,加载一个类可能发生的查找行为和委派关系会比上图中显示的复杂,类加载时的查找规则如下:

        1)以java.*开头的类,委派给父类加载器加载

        2)否则,委派列表名单内的类,委派给父类加载器加载

        3)否则,Import列表中的类,委派给Export这个类的Bundle的类加载器加载

        4)否则,查找当前Bundle的ClassPath,使用自己的类加载器加载

        5)否则,查找是否在自己的Fragment Bundle中,如果是,则委派给Fragment bundle的类加载器加载

        6)否则,查找Dynamic Import列表的Bundle,委派给对应Bundle的类加载器加载

        7)否则,查找失败

      从之前的图可以看出,在OSGi里面,加载器的关系不再是双亲委派模型的树形架构,而是已经进一步发展成了一种更复杂的、运行时才能确定的网状结构。

    展开全文
  • OSGi之所以能够实现模块热插拔和模块内部可见性的精准控制都... 当请求bundle的类加载加载类或资源时,必须按以下顺序执行搜索:如果或资源在java.*包中,则将请求委托给父类加载器; 否则,继续下一步搜索。 ...
  • OSGI类加载问题

    2013-07-23 11:11:20
    看《深入理解OSGI》(作者:周志明)书遇到的一个问题,OSGI中Fragement Bundle木有自己的类加载器,它与Host Bundle公用同一个累加器器,但是书中说在OSGI环境下,当收到类加载请求时,OSGI将按照下面的顺序进行...
  • 参考链接:https://blog.csdn.net/fuzhongmin05/article/details/57404890 Java类加载机制类加载器 虚拟机设计团队把类加载阶段中的“通过一个的全限定名来获取描述此类的二进制字节流”这个动作放到Java虚拟机...
  • 首先一个思维导图来看下Tomcat的类加载机制和JVM类加载机制的过程 类加载  在JVM中并不是一次性把所有的文件都加载到,而是一步一步的,按照需要来加载。  比如JVM启动时,会通过不同的类加载加载不同的...
  • OSGI类加载机制

    2015-11-08 16:03:53
    声明:以下为转载内容 原... 思路 ...OSGi每个模块都有自己独立的classpath。...是因为OSGi采取了不同的类加载机制: ...OSGi为每个bundle提供一个类加载器,该加载器能够看到bundle Jar文件内部的和资源;为了让bun
  • OSGI类加载

    2018-09-13 11:16:13
    4) Bundle路径(Bundle Classpath):指的是Bundle中资源在OSGi环境中加载顺序,它基于Entry Path来构建。下图是一个简单的样例: 从上图中能看出,在MANIFEST.MF中,Bundle-ClassPath由以逗号“,”...
  • OSGI类加载架构

    2018-12-28 07:26:24
    1、父类加载器:java平台提供,如启动类加载器BootstrapClassLoader、扩展类加载器Extension ClassLoader和应用程序类加载器Application ClassLoader; 2、Bundle类加载器:每个bundle都有自己的类加载器,加载本...
  • OSGi类加载架构并未遵循Java所推荐的双亲委派模型,他的类加载器通过严谨定义的规则从Bundle的一个子集中加载类。除了Fragment Bundle外,每一个被正确解析的Bundle都有一个独立的类加载器支持,这些类加载器之间...
  • 了解线程上下文类加载器、服务器类加载器、OSGI原理 线程上下文类加载器 双亲委托机制以及默认类加载器的问题 一般情况下, 保证同一个中所关联的其他都是由当前类加载器所加载的.。比如,ClassA本身在...
  • OSGi模块化框架是很早就出来的一个插件化框架,最早Eclipse用它而出名,但这些年也没有大热虽然OSGi已经发布了版本1到版本5。现在用的最多的,也是本文讲述基于的是Equinox的OSGi实现,同时也是Eclipse核心,Spring ...
  • 正如之前反反复复在websphere里设置应用的类加载顺序的时候,从来没去想这个调整真正改变了什么。   1. java的加载器: JAVA加载器分为3层——引导加载器、扩展加载器、应用程序加载器,加载遵循"...
  • 2.5 OSGi类加载架构 OSGi为Java平台提供了动态模块化的特性,但是它并没有对Java的底层实现如类库和Java虚拟机等进行修改,OSGi实现的模块间引用与隔离、模块的动态启用与停用的关键在于它扩展的类加载架构。OSGi...
  • 一、java类加载器机制 jvm想执行.class文件第一...java类加载器的作用是在运行时加载类。 Java类加载机制指的是将java编译后生成的.class文件中的二进制数据读入到内存中,将其放到jvm构造的运行时数据区内存...
  • WAS 调整类加载顺序

    千次阅读 2013-01-30 18:07:27
    给自己备忘下该问题,解决方法、was类加载机制 1. java的类加载器: JAVA类加载器分为3层——引导加载器、扩展加载器、应用程序加载器,类加载遵循"父委托模式". 引导加载器(Bootstrap): 加载/jre/lib 下的...
  • 有了线程上下文类加载器,就可以做一些“舞弊”的事情了,JNDI服务使用这个线程上下文类加载器去加载所需要的 SPI代码,也就是父类加载器请求子类加载器去完成类加载的动作,这种行为实际上就是打通了双亲委派模型...
  • 类加载器: 双亲委派模型下的类加载机制: ​​ JVM启动的时候,Java 开始使用如下三种类加载器: 1. 根类加载器 Bootstrap 负责装载最核心的Java,比如Object、System、 String等,用 C++ 来实现的,不存在于 JVM ...
  • 解决了各个类加载器的基础的统一问题(越基础的由越上层的加载器进行加载)  , 基础之所以称为 “ 基础 ” ,是因为它们总是作为被用户代码调用的API  ,但世事往往没有绝对的完美,如果 基础又要调用回...
  • 7.4.1 类加载器 比较两个是否相等的两个条件:同一个类加载加载、两个来源于同一个class文件。public class ClassLoaderTest { public static void main(String[] args) throws ClassNotFoundException,...
  • Tomcat类加载器以及应用间class隔离与共享 类加载器 参考文章 打破双亲委派模型 JNDI JNDI 的理解 JNDI是 Java 命名与文件夹接口(Java Naming and Directory Interface),在J2EE规范中是重要的规范之中的一....
  • 一.JVM 类加载器: 一个在使用前,如何通过调用静态字段,静态方法,或者new一个实例对象,第一步就是需要类加载,然后是连接和初始化,最后才能使用。 从被加载到虚拟机内存中开始,到卸载出内存为止,它的...
  • 把大型系统移植到OSGi架构上常常意味着解决复杂的类加载问题。这篇文章专门研究了面向这个领域最难问题的几个框架:有关动态代码生成的框架。这些框架 也都是些超酷的框架:AOP包装器,ORM映射器以及服务代理生成器...
  • package java_223_JVM核心机制_线程上下文类加载器_web服务器类加载机制_OSGI技术模块开发原理介绍_练习; import java_219_JVM核心机制_类加载全过程_初始化时机_的主动引用和被动引用_静态初始化块执行顺序问题_...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 4,161
精华内容 1,664
关键字:

osgi类加载顺序