java classloader

来源:互联网 发布:大麦盒子电视直播软件 编辑:程序博客网 时间:2024/03/29 16:06

一、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/
0 0
原创粉丝点击