java classloader

一、classLoader加载机制
JVM类加载器加载机制并不是继承关系,而是委派关系,之前意识中一直当作是继承关系。自定义classloader的时候,通常会传一个parent classLoader,看见parent就想当然的理解成继承关系,直到在写code时,发现不同classLoader的用各自的parent加载的class,都是用的相同的classLoader实例,然后才把这个潜意识改变过来。
1.默认
JVM默认类加载机制是自下而上的委派方式,即加载某个class时,先从parent classLoader加载,如果该parent还有parent,则依次向上递归,若还加载不到,此时自己再加载。例如,我们通过命令行+classpath参数启动的程序,就是该流程,下面引用java.lang.ClassLoader的loadClass方法代码:

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 查看是否被加载过
            Class c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {//若parent loader不空,在委托parent加载
                    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) {
                    // 若还没有加载到,此时自己再加载
                    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;
        }
    }

2.定制
某些场合下,优先从本地加载class,若加载不到,再委托父类加载。 JavaEE规范则推荐每个类加载模块先加载本类加载的内容,若果加载不到,才尝试从parent中加载。比如tomcat,jetty就是采用这种加载机制。下面贴出jetty的webAppClassLoader的加载class片段:

protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
    {
        Class<?> c= findLoadedClass(name);
        ClassNotFoundException ex= null;
        boolean tried_parent= false;
        //判断是否是系统类,即AppClassLoader加载的
        boolean system_class=_context.isSystemClass(name);
        //判断是否是jetty本身类,即jetty代码
        boolean server_class=_context.isServerClass(name);

        if (system_class && server_class)
        {
            return null;
        }
        //isParentLoaderPriority标示是否先委托parent加载,默认false
        if (c == null && _parent!=null && (_context.isParentLoaderPriority() || system_class) && !server_class)
        {
            tried_parent= true;
            try
            {
                c= _parent.loadClass(name);
                if (LOG.isDebugEnabled())
                    LOG.debug("loaded " + c);
            }
            catch (ClassNotFoundException e)
            {
                ex= e;
            }
        }

        if (c == null)
        {
            try
            {
                //自己加载各个war包下的类
                c= this.findClass(name);
            }
            catch (ClassNotFoundException e)
            {
                ex= e;
            }
        }

        if (c == null && _parent!=null && !tried_parent && !server_class )
            c= _parent.loadClass(name);

        if (c == null)
        if (resolve)
            resolveClass(c);

        if (LOG.isDebugEnabled())
            LOG.debug("loaded " + c+ " from "+c.getClassLoader());

        return c;
    }

二、classLoader加载体系
第一节中提到2种类加载机制,一个种是自下而上的委派模式,另外一种则是定制,优先从本地类加载模块加载。下面就介绍几种类加载器:
1.bootstrap classLoader
bootstrap加载器是用C++语言写的,它是在Java虚拟机启动后初始化的,它主要负责加载%JAVA_HOME%/jre/lib,-Xbootclasspath参数指定的路径以及%JAVA_HOME%/jre/classes中的类。

2.extension classLoader
Bootstrap loader加载ExtClassLoader,并且将ExtClassLoader的父加载器设置为Bootstrap loader。ExtClassLoader是用Java写的,具体来说就是 sun.misc.Launcher$ExtClassLoader,ExtClassLoader主要加载%JAVA_HOME%/jre/lib/ext,此路径下的所有classes目录以及java.ext.dirs系统变量指定的路径中类库。

3.system app classLoader
AppClassLoader也是用Java写成的,它的实现类是 sun.misc.Launcher$AppClassLoader,另外我们知道ClassLoader中有个getSystemClassLoader方法,此方法返回的正是AppclassLoader.AppClassLoader主要负责加载classpath所指定的位置的类或者是jar文档,它也是Java程序默认的类加载器。

4.customer classLoader
就是开发自定义的classLoader,通常它的parent classLoader一般是AppClassLoader。

三、classLoader实践与样例
在开发中常见的几种classLoader错误
1.NoClassDefFoundError
NoClassDefFoundError
classloader没有找到该类的定义,一般是该类的jar包没有加载到classpath路径下。

2.NoSuchMethodError
NoSuchMethodError情况在开发过程中也会经常遇到。在项目中该类所在的jar包有多个版本,比如maven工程中,jar包版本的间接依赖导致版本仲裁的时候,选择的版本可能并不是我们所需要的版本,就可能会出现这个错误,通过maven tree把jar包依赖树打印出来排除掉即可。

3.ClassCastException
在JVM中,确定一个类的实例类型是:类全名+类加载器。那么出现ClassCastException一般是类型转换的时候遇到问题,即class实例类型不对。在双亲委派模式下一般不会出现,classLoader都交给系统处理。当遇到我们自定义的classLoader的时候,就可能会出现这种问题。

4.LinkageError
通常这种错误比较难排查,其实和ClassCastException类型本质一样,下面是我摘自他人的一个样例:

public class HandleUtils {
    public void m(Param param) {
        param.generate();
    }


    public void print(){
        System.out.println("hello world!");
    }

}
public class Param {
    private String name;

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Param(){
        System.out.println("Param: "+Param.class.getClassLoader());
    }

    public Param2 generate() {
        System.out.println("Param2: "+Param2.class.getClassLoader());
        return new Param2();
    }

//    public Param generate() {
//        return new Param();
//    }
}
public class Param2 extends Param {

    public Param2 generate() {
        System.out.println("i am param2.");
        return new Param2();
    }

}
public class TestLinkError {

    public static void main(String[] args) {
        try {
            TestLinkError.test();
        } catch (Exception e) {
            e.printStackTrace();
//            System.out.println(e);
        }
    }

    public static void test() throws Exception {

        // cl1在加载HandleUtils和Param时将会使用AppClassLoader
        URLClassLoader cl1 = new URLClassLoader(new URL[] { new File("target/test-classes").toURI().toURL() }, null) {

            @Override
            public Class<?> loadClass(String name) throws ClassNotFoundException {
                if ("com.murdock.classloader.linkageerror.HandleUtils".equals(name)) {
                    return ClassLoader.getSystemClassLoader().loadClass(name);
                }

                if ("com.murdock.classloader.linkageerror.Param".equals(name)) {
                    return ClassLoader.getSystemClassLoader().loadClass(name);
                }

                return super.loadClass(name);
            }

        };

        ClassLoader.getSystemClassLoader().loadClass("com.murdock.classloader.linkageerror.Param2");
        HandleUtils hu = (HandleUtils) cl1.loadClass("com.murdock.classloader.linkageerror.HandleUtils").newInstance();
        hu.m((Param) cl1.loadClass("com.murdock.classloader.linkageerror.Param2").newInstance());
    }

四、其他

  1. 类加载器方面的文章 http://zeroturnaround.com/rebellabs/rebel-labs-tutorial-do-you-really-get-classloaders/4/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值