Java Classloader详解

1、Java中的class加载机制有如下三个特性:java

一、全盘负责制 bootstrap

    “全盘负责”是指当一个ClassLoader装载一个类时,除非显示地使用另外一个ClassLoader,则该类所依赖及引用的类也由这个CladdLoader载入。缓存

例如,系统类加载器AppClassLoader加载入口类(含有main方法的类)时,会把main方法所依赖的类及引用的类也载入,依此类推。“全盘负责”机制也可称为当前类加载器负责机制。显然,入口类所依赖的类及引用的类的当前类加载器就是入口类的类加载器。安全

二、双亲委派制(Parent Delegation)app

1) 委托机制的意义ide

    主要是出于安全性考虑,确保Java的核心类在内存中只有一份字节码,好比两个类A和类B都要加载java.lang.System类,经过双亲委派,系统只会加载一次java.lang.System,即便用户重写了java.lang.System,也不会有机会被加载到,除非你重写ClassLoader。不过有时候为了作容器隔离,须要在JVM中对同一个Class有多份字节码,例如OSGI和Pandora技术,后面会详细谈到。this

2) 委托机制是必须的吗?spa

    “双亲委派”机制只是Java推荐的机制,并非强制的机制。咱们能够继承java.lang.ClassLoader类,实现本身的类加载器。若是想保持双亲委派模型,就应该重写findClass(name)方法;若是想破坏双亲委派模型,能够重写loadClass(name)方法。使用本身的类加载器,有不少高级玩法,例如OSGI和Pandora的隔离机制,就是经过自定义ClassLoader来实现的。线程

3) 如何实现双亲委派?3d

  默认的ClassLoader的loadClass()实现方式是双亲委派模型,咱们能够继承ClassLoader去自定义本身的ClassLoader,若是不重写loadClass方法,那么默认也是双亲委派的。例如,URLClassLoader只是实现了findClass( ),而loadClass( )仍是继承ClassLoader的,因此其依然是Parent Delegation的。下面是ClassLoader.loadClass( )源码,看下双亲委派是怎么实现的。

 

 

1)Check这个class是否被装载过,若是有直接返回。

装载过的类是被缓存起来的,这样确保了同一个类不会被加载两次,不过有一个问题,用什么来做为class缓存的key呢?在JVM中,class是用类全名(包名+类名) 再加上加载这个类的classLoader来惟一标识的,例如class的类全名是C1,classLoader是L1,那么这个class instance在JVM中的key就是(C1, L1),此时另外一个classLoader L2也加载了该类,那么将会有另外一个class instance (C1, L2),这两个class instance是不一样的type,若是这两个class的object作赋值操做的话,会出现ClassCastException。

2)尝试从parent classloader去加载类。

3)若是parent是null(当parent是bootstrap时就是null了),试图从BootstrapClassLoader的native方法去加载类。

4)在上面尝试都失败的状况下,尝试本身去加载。

 

三、按需加载 (On-demand Loading)  

 

    何时Class会被JVM加载呢? 回答是只有当class被用到时,才会被load,例如new instance,调用其static变量和方法,或使用反射调用其class对象。

 

这个很容易验证,在启动参数里加上-verbose:class,  就能够清晰看到class是什么时候被加载的。

2、JVM中classloader加载class的顺序

3、ContextClassloader的用处

 

1)什么是ContextClassLoader

 

Thread的一个属性,能够在运行时,经过setContextClassLoader方法来指定一个合适的classloader做为这个线程的contextClassLoader,而后在任何地方经过getContextClassLoader方法来得到此contextClassLoader,用它载入咱们所须要的Class。若是没有被显示set过,默认是system classloader。利用这个特性,咱们能够“打破”classloader委托机制,父classloader能够得到当前线程的contextClassLoader,而这个contextClassLoader能够是它的子classloader或者其余的classloader。

2) 为何要使用ContextClassLoader

 

Thread context classloaders provide a back door around the classloading delegation scheme.

 

Take JNDI for instance: its guts are implemented by bootstrap classes in rt.jar (starting with J2SE 1.3), but these core JNDI classes may load JNDI providers implemented by independent vendors and potentially deployed in the application's -classpath. This scenario calls for a parent classloader (the primordial one in this case) to load a class visible to one of its child classloaders (the system one, for example). Normal J2SE delegation does not work, and the workaround is to make the core JNDI classes use thread context loaders, thus effectively "tunneling" through the classloader hierarchy in the direction opposite to the proper delegation.

 

这个后门在SPI的实现中颇有用,由于接口类是在parent classloader中加载的,而实现类是由它的child classloader加载的,使用contextClassLoader能够绕过双亲委派,达到在parent中使用child classloader去load class的目的。其过程以下图所示:

 

这样的示例在JDK中很常见,例如JNDI和JAXP都是经过这样的方式去加载具体的provider的。例如

javax.xml.ws.spi.FactoryFinder
Object find(String factoryId, String fallbackClassName){
ClassLoader classLoader;
        try {
            // get context classloader, mostly it's system classloader, but it could be user-defined classloader as well.
            classLoader = Thread.currentThread().getContextClassLoader();
        } catch (Exception x) {
            throw new WebServiceException(x.toString(), x);
        }
        String serviceId = "META-INF/services/" + factoryId;
        // try to find services in CLASSPATH
        // Note that if it's not system classloader, this will invoke user-defined classloader's findResource( ) to find services when all its parents failed.
        try {
            InputStream is=null;
            if (classLoader == null) {
                is=ClassLoader.getSystemResourceAsStream(serviceId);
            } else {
                is=classLoader.getResourceAsStream(serviceId);
            }
.....
    return newInstance(fallbackClassName, classLoader);
}
相关文章
相关标签/搜索