ClassLoader 详解及应用

一 .定义:

 * A class loader is an object that is responsible for loading classes. The
 * class ClassLoader  is an abstract class.  Given the 
 * binary name of a class, a class loader should attempt to
 * locate or generate data that constitutes a definition for the class.  A
 * typical strategy is to transform the name into a file name and then read a
 * "class file" of that name from a file system.

类加载器需要完成的最终功能是定义一个Java类,即把Java字节代码转换成JVM中的java.lang.Class类的对象。但是类加载的过程并不是这么简单。Java类加载器有两个比较重要的特征:层次组织结构代理模式。层次组织结构指的是每个类加载器都有一个父类加载器,通过getParent()方法可以获取到。类加载器通过这种父亲-后代的方式组织在一起,形成树状层次结构。代理模式则指的是一个类加载器既可以自己完成Java类的定义工作,也可以代理给其它的类加载器来完成。由于代理模式的存在,启动一个类的加载过程的类加载器和最终定义这个类的类加载器可能并不是一个。前者称为初始类加载器,而后者称为定义类加载器。两者的关联在于:一个Java类的定义类加载器是该类所导入的其它Java类的初始类加载器。比如类A通过import导入了类 B,那么由类A的定义类加载器负责启动类B的加载过程。

类加载器的一个重要用途是在JVM中为相同名称的Java类创建隔离空间。在JVM中,判断两个类是否相同,不仅是根据该类的二进制名称,还需要根据两个类的定义类加载器。只有两者完全一样,才认为两个类的是相同的。因此,即便是同样的Java字节代码,被两个不同的类加载器定义之后,所得到的Java类也是不同的。如果试图在两个类的对象之间进行赋值操作,会抛出java.lang.ClassCastException。这个特性为同样名称的Java类在JVM中共存创造了条件。在实际的应用中,可能会要求同一名称的Java类的不同版本在JVM中可以同时存在。通过类加载器就可以满足这种需求。这种技术在OSGi中得到了广泛的应用。

二.Classloader的分类:

 


分别解释一下:

(1)引导类加载器:加载java核心库,用原生代码实现。
(2)扩展类加载器:加载java扩展库。Java虚拟机的实现会提供一个扩展库的目录,扩展类加载器会自动从此目录中查找并加载java类。
(3)系统类加载器:根据java应用定义的类路径(classpath)查找并加载java类。其父类为扩展类加载器。
(4)自定义加载器:由用户定义并且继承自ClassLoader类的加载器,其父类加载器一般为系统类加载器。

关于上面提到的几个路径我们可以通过系统属性得到:

for (Map.Entry<Object, Object> entry:System.getProperties().entrySet()) {
    System.out.println(entry.getKey()+"\t"+entry.getValue()); 
}
打印内容如下:

java.runtime.name Java(TM) SE Runtime Environment
sun.boot.library.path C:\Program Files\Java\jdk1.6.0_17\jre\bin
java.vm.version 14.3-b01
java.vm.vendor Sun Microsystems Inc.
java.vendor.url http://java.sun.com/
path.separator ;
java.vm.name Java HotSpot(TM) Client VM
file.encoding.pkg sun.io
sun.java.launcher SUN_STANDARD
user.country CN
sun.os.patch.level Service Pack 3
java.vm.specification.name Java Virtual Machine Specification
user.dir D:\admin\src\workspace\setest
java.runtime.version 1.6.0_17-b04
java.awt.graphicsenv sun.awt.Win32GraphicsEnvironment
java.endorsed.dirs C:\Program Files\Java\jdk1.6.0_17\jre\lib\endorsed
os.arch x86
java.io.tmpdir C:\DOCUME~1\admin\LOCALS~1\Temp\
line.separator
java.vm.specification.vendor Sun Microsystems Inc.
user.variant
os.name Windows XP
sun.jnu.encoding GBK
java.library.path ...... 省略
java.specification.name Java Platform API Specification
java.class.version 50.0
sun.management.compiler HotSpot Client Compiler
os.version 5.1
user.home C:\Documents and Settings\..
user.timezone
java.awt.printerjob sun.awt.windows.WPrinterJob
file.encoding GBK
java.specification.version 1.6
java.class.path ........省略(由系统类加载器加载)
user.name baiyc
java.vm.specification.version 1.0
java.home C:\Program Files\Java\jdk1.6.0_17\jre
sun.arch.data.model 32
user.language zh
java.specification.vendor Sun Microsystems Inc.
awt.toolkit sun.awt.windows.WToolkit
java.vm.info mixed mode, sharing
java.version 1.6.0_17
java.ext.dirs (有扩展类加载器加载)

C:\Program Files\Java\jdk1.6.0_17\jre\lib\ext;

C:\WINDOWS\Sun\Java\lib\ext

sun.boot.class.path (由引导类加载器加载)

C:\Program Files\Java\jdk1.6.0_17\jre\lib\resources.jar;

C:\Program Files\Java\jdk1.6.0_17\jre\lib\rt.jar;

C:\Program Files\Java\jdk1.6.0_17\jre\lib\sunrsasign.jar;

C:\Program Files\Java\jdk1.6.0_17\jre\lib\jsse.jar;

C:\Program Files\Java\jdk1.6.0_17\jre\lib\jce.jar;

C:\Program Files\Java\jdk1.6.0_17\jre\lib\charsets.jar;

C:\Program Files\Java\jdk1.6.0_17\jre\classes

java.vendor Sun Microsystems Inc.
file.separator \
java.vendor.url.bug http://java.sun.com/cgi-bin/bugreport.cgi
sun.io.unicode.encoding UnicodeLittle
sun.cpu.endian little
sun.desktop windows
sun.cpu.isalist

三.classloader在tomcat(6.0)中的应用

上面我们简单介绍了一些关于classloader的常识,接下来我们来结合tomcat来看一下classloader在web容器中的应用:

当tomcat启动后将会创建一组以父子关系组织的classloader对象其各classloader的关系如下图:


Bootstrap
          |
       System
          |
       Common
      /      \
 Catalina   Shared
             /   \
        Webapp1  Webapp2 ...


1.bootstrap:相当于引导类加载器和扩展类加载器。

2.System:相当于系统类加载器,这里加载的类对于tomcat本身内部的类和应用中的类都是可见的。在tomcat的启动脚本中,并没有加载默认的classpath中的类(将ClassPath清空重新set)而是从以下路径进行的加载:

$CATALINA_HOME/bin/bootstrap.jar 
$CATALINA_BASE/bin/tomcat-juli.jar 和$CATALINA_HOME/bin/tomcat-juli.jar 
$CATALINA_HOME/bin/commons-daemon.jar 
注:

    tomcat-juli.jar和commons-daemon.jar 在tomcat.bat中并没有出现,这两个jar是在bootstrap.jar的MANIFEST.MF文件中引用的。

    CATALINA_HOME是Tomcat的安装目 录,CATALINA_BASE是Tomcat的工作目录,一般情况下这两个路径一致。

3.Common:该类加载器负载加载tomcat附加类,该加载器加载的类对于tomcat内部的类和应用中的类都是可见的。

一般情况下,应用中的类不应该放在Common类加载路径中。这个路径是在$CATALINA_BASE/conf/catalina.properties

中定义的。


common.loader=${catalina.home}/lib,${catalina.home}/lib/*.jar
默认加载如下jar


annotations-api.jar — JavaEE annotations classes.
catalina.jar — Implementation of the Catalina servlet container portion of Tomcat.
catalina-ant.jar — Tomcat Catalina Ant tasks.
catalina-ha.jar — 高可用 package.
catalina-tribes.jar — 组通信 package.
ecj-*.jar — Eclipse JDT Java compiler.
el-api.jar — EL 2.1 API.
jasper.jar — Tomcat Jasper JSP Compiler and Runtime.
jasper-el.jar — Tomcat Jasper EL implementation.
jsp-api.jar — JSP 2.1 API.
servlet-api.jar — Servlet 2.5 API.
tomcat-coyote.jar — Tomcat connectors and utility classes.
tomcat-dbcp.jar — 数据库连接池
tomcat-i18n-**.jar — Optional JARs containing resource bundles for other languages. As default bundles are also included in each individual JAR, they can be safely removed if no internationalization of messages is needed.

4.webapp xx :相当于用户自定义加载器,为每个部署在独立tomcat实例上的web应用而创建。该加载器加载的类对于自身应用中的类都是可见的但对于其他web应用不可见,它负责加载下面路径中的类

/WEB-INF/classes
/WEB-INF/lib

和java2中的加载类的代理模式方式不同,webapp xx 类加载器采用的是另一种类加载模式(Servlet 2.4规范9.7.2节 web Application Classloader中建议使用这种方式),当一个request对象加载一个由webappxx Classloader负责加载的类时,webappxx Classloader 将首先在本地库(WEB-INF)进行搜索,与传统的委托给父加载器进行搜索的方式不同。

四.源码级别上的理解

为了对tomcat中classloader有更直观的理解,我们来分析一下tomcat的源码:

tomcat 初始化时调用initClassLoaders对Classloader进行初始化:

private void initClassLoaders() {
        try {
            //初始化CommonClassLoader
            commonLoader = createClassLoader("common", null);
            if( commonLoader == null ) {
                commonLoader=this.getClass().getClassLoader();
            }
            //初始化其它两个类加载器
            catalinaLoader = createClassLoader("server", commonLoader);
            sharedLoader = createClassLoader("shared", commonLoader);
        } catch (Throwable t) {
            log.error("Class loader creation threw exception", t);
            System.exit(1);
        }
    }

我们可以看到,此处初始化了三个类加载器,并且catalinaLoader和sharedLoader都以commonLoader作为父类加载器,在这个方法中,将核心的业务交给了createClassLoader方法来实现:

private ClassLoader createClassLoader(String name, ClassLoader parent)
        throws Exception {
        //读取配置属性,相关的配置属性在catalina.properties文件中,见common 加载器说明
        String value = CatalinaProperties.getProperty(name + ".loader");
        //如果没有对应的配置,将不会创建新的类加载器,而是返回传入的父类加载器
        if ((value == null) || (value.equals("")))
            return parent;

       //解析得到的配置文件,确定本ClassLoader要加载那些目录下的资源和JAR包等
        StringTokenizer tokenizer = new StringTokenizer(value, ",");
        while (tokenizer.hasMoreElements()) {
            String repository = tokenizer.nextToken();

        //此处省略的代码为将配置文件中的${catalina.base}、${catalina.home}等变量转
        //换为绝对路径

        //格式化得到的位置路径和类型
        String[] locations = (String[]) repositoryLocations.toArray(new String[0]);
        Integer[] types = (Integer[]) repositoryTypes.toArray(new Integer[0]);
        
        //生成真正的类加载器 
        ClassLoader classLoader = ClassLoaderFactory.createClassLoader
            (locations, types, parent);

       //以下的代码为将生成的类加载器注册为MBean

        return classLoader;
    }

而每个类加载器所加载的路径或JAR是在catalina.properties文件中定义的,默认的配置如下:

common.loader=
${catalina.base}/lib,${catalina.base}/lib/*.jar,${catalina.home}/lib,${catalina.home}/lib/*.jar
server.loader=
shared.loader=

按照默认的配置,catalinaLoader和sharedLoader的配置项为空,因此不会创建对应的ClassLoader,而只会创建CommonClassLoader,该类加载器对应的Java实现类为:org.apache.catalina.loader. StandardClassLoader,该类继承自org.apache.catalina.loader. URLClassLoader,有关Tomcat基础类都会有该类加载器加载(见common类加载器路径说明)。例如在Bootstrap的init方法中,会调用Catalina类的init方法来完成相关操作:

public void init() throws Exception{

       //将当前线程的类加载器设置为catalinaLoader
        Thread.currentThread().setContextClassLoader(catalinaLoader);

        SecurityClassLoad.securityClassLoad(catalinaLoader);

       //使用catalinaLoader(catalinaLoader默认为空,由父加载器CommonClassLoader)来加载Catalina类
        Class startupClass =
            catalinaLoader.loadClass
            ("org.apache.catalina.startup.Catalina");
        Object startupInstance = startupClass.newInstance();

        //调用Catalina的setParentClassLoader方法,设置为sharedLoader
        String methodName = "setParentClassLoader";
        Class paramTypes[] = new Class[1];
        paramTypes[0] = Class.forName("java.lang.ClassLoader");
        Object paramValues[] = new Object[1];
        paramValues[0] = sharedLoader;
        Method method =
            startupInstance.getClass().getMethod(methodName, paramTypes);
        method.invoke(startupInstance, paramValues);

        catalinaDaemon = startupInstance;
    }

以上为基础的三个类加载器的初始化过程(初始化之后jvm的堆内存中将有这三个类加载器的实例(根据上面的情况只有一个commonClassloader(StandardClassLoader.class)实例),在main线程的线程栈中存有这个实例的引用,perm区中保存的为StandardClassLoader.class文件mete信息)。在每个Web应用初始化的时候,StandardContext对象代表每个Web应用,它会使用WebappLoader类来加载Web应用,而WebappLoader中会初始化org.apache.catalina.loader. WebappClassLoader来为每个Web应用创建单独的类加载器,当处理请求时,容器会根据请求的地址解析出由哪个Web应用来进行对应的处理,进而将当前线程的类加载器设置为请求Web应用的类加载器。让我们看一下WebappClassLoader的核心方法,也就是loadClass:


public synchronized Class loadClass(String name, boolean resolve)
        throws ClassNotFoundException {

        Class clazz = null;

        //首先检查已加载的类
        // (0) Check our previously loaded local class cache
        clazz = findLoadedClass0(name);
        if (clazz != null) {
            if (log.isDebugEnabled())
                log.debug("  Returning class from cache");
            if (resolve)
                resolveClass(clazz);
            return (clazz);
        }

        // (0.1) Check our previously loaded class cache
        clazz = findLoadedClass(name);
        if (clazz != null) {
            if (log.isDebugEnabled())
                log.debug("  Returning class from cache");
            if (resolve)
                resolveClass(clazz);
            return (clazz);
        }

        // (0.2) Try loading the class with the system class loader, to prevent
        //       the webapp from overriding J2SE classes
        try {
            clazz = system.loadClass(name);
            if (clazz != null) {
                if (resolve)
                    resolveClass(clazz);
                return (clazz);
            }
        } catch (ClassNotFoundException e) {
            // Ignore
        }

        // (0.5) Permission to access this class when using a SecurityManager
        if (securityManager != null) {
            int i = name.lastIndexOf('.');
            if (i >= 0) {
                try {
                    securityManager.checkPackageAccess(name.substring(0,i));
                } catch (SecurityException se) {
                    String error = "Security Violation, attempt to use " +
                        "Restricted Class: " + name;
                    log.info(error, se);
                    throw new ClassNotFoundException(error, se);
                }
            }
        }

        boolean delegateLoad = delegate || filter(name);

        //Tomcat允许按照配置来确定优先使用本Web应用的类加载器加载还是使用父类
        //加载器来进行类加载,此处先使用父类加载器进行加载
        // (1) Delegate to our parent if requested

        if (delegateLoad) {
            if (log.isDebugEnabled())
                log.debug("  Delegating to parent classloader1 " + parent);
            ClassLoader loader = parent;
            if (loader == null)
                loader = system;
            try {
                clazz = loader.loadClass(name);
                if (clazz != null) {
                    if (log.isDebugEnabled())
                        log.debug("  Loading class from parent");
                    if (resolve)
                        resolveClass(clazz);
                    return (clazz);
                }
            } catch (ClassNotFoundException e) {
                ;
            }
        }
        //使用本地的类加载器进行加载
        // (2) Search local repositories
        if (log.isDebugEnabled())
            log.debug("  Searching local repositories");
        try {
            clazz = findClass(name);
            if (clazz != null) {
                if (log.isDebugEnabled())
                    log.debug("  Loading class from local repository");
                if (resolve)
                    resolveClass(clazz);
                return (clazz);
            }
        } catch (ClassNotFoundException e) {
            ;
        }
        //如果没有特殊配置的话,使用父类加载器加载类
        // (3) Delegate to parent unconditionally
        if (!delegateLoad) {
            if (log.isDebugEnabled())
                log.debug("  Delegating to parent classloader at end: " + parent);
            ClassLoader loader = parent;
            if (loader == null)
                loader = system;
            try {
                clazz = loader.loadClass(name);
                if (clazz != null) {
                    if (log.isDebugEnabled())
                        log.debug("  Loading class from parent");
                    if (resolve)
                        resolveClass(clazz);
                    return (clazz);
                }
            } catch (ClassNotFoundException e) {
                ;
            }
        }
        //若最终类还是没有找到,抛出异常
        throw new ClassNotFoundException(name);

    }

以上就是Web应用中类加载的机制。在默认情况下,WebappClassLoader的父类加载器就是CommonClassLoader,但是我们可以通过修改catalina.properties文件来设置SharedClassLoader,从而实现多个Web应用共用类库的效果。



转载于:https://my.oschina.net/pkm2012/blog/85393

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值