获得classloader的途径
1. 获得当前类的classloader
clazz.getclassloader()
2. 获得当前线程上下文的classloader
thread.currentthread().getcontextclassloader();
3. 获得系统的classloader
classloader.getsystemclassloader()
4. 获得调用者的classloader
drivermanager.getcallerclassloader
classloader源码解析
(推荐学习:java视频教程)
概述
类加载器是用于加载类的对象,classloader是一个抽象类。如果我们给定了一个类的二进制名称,类加载器应尝试去定位或生成构成定义类的数据。一种典型的策略是将给定的二进制名称转换为文件名,然后去文件系统中读取这个文件名所对应的class文件。
每个class对象都会包含一个定义它的classloader的一个引用。
数组类的class对象,不是由类加载器去创建的,而是在java运行期jvm根据需要自动创建的。对于数组类的类加载器来说,是通过class.getclassloader()返回的,与数组当中元素类型的类加载器是一样的;如果数组当中的元素类型是一个原生类型,数组类是没有类加载器的【代码一】。
应用实现了classloader的子类是为了扩展jvm动态加载类的方式。
类加载器典型情况下时可以被安全管理器所使用去标识安全域问题。
classloader类使用了委托模型来寻找类和资源,classloader的每一个实例都会有一个与之关联的父classloader,当classloader被要求寻找一个类或者资源的时候,classloader实例在自身尝试寻找类或者资源之前会委托它的父类加载器去完成。虚拟机内建的类加载器,称之为启动类加载器,是没有父加载器的,但是可以作为一个类加载器的父类加载器【双亲委托机制】。
支持并发类加载的类加载器叫做并行类加载器,要求在初始化期间通过classloader.registerasparallelcapable 方法注册自身,classloader类默认被注册为可以并行,但是如果它的子类也是并行加载的话需要单独去注册子类。
在委托模型不是严格的层次化的环境下,类加载器需要并行,否则类加载会导致死锁,因为加载器的锁在类加载过程中是一直被持有的。
通常情况下,java虚拟机以平台相关的形式从本地的文件系统中加载类,比如在unix系统,虚拟机从classpath环境所定义的目录加载类。
然而,有些类并不是来自于文件;它们是从其它来源得到的,比如网络,或者是由应用本身构建【动态代理】。定义类(defineclass )方法会将字节数组转换为class的实例,这个新定义类的实例可以由class.newinstance创建。
由类加载器创建的对象的方法和构造方法可能引用其它的类,为了确定被引用的类,java虚拟机会调用最初创建类的类加载器的loadclass方法。
二进制名称:以字符串参数的形式向calssloader提供的任意一个类名,必须是一个二进制的名称,包含以下四种情况
"java.lang.string" 正常类"javax.swing.jspinner$defaulteditor" 内部类"java.security.keystore\(builder\)filebuilder$1" keystore的内部类builder的内部类filebuilder的第一个匿名内部类"java.net.urlclassloader$3$1" urlclassloader类的第三个匿名内部类的第一个匿名内部类代码一:
public class test12 {    public static void main(string[] args) {        string[] strings = new string[6];        system.out.println(strings.getclass().getclassloader());        // 运行结果:null        test12[] test12s = new test12[1];        system.out.println(test12s.getclass().getclassloader());        // 运行结果:sun.misc.launcher$appclassloader@18b4aac2        int[] ints = new int[2];        system.out.println(ints.getclass().getclassloader());        // 运行结果:null    }}
loadclass方法
loadclass的源码如下, loadclass方法加载拥有指定的二进制名称的class,默认按照如下顺序寻找类:
a)调用findloadedclass(string)检查这个类是否被加载
b)调用父类加载器的loadclass方法,如果父类加载器为null,就会调用启动类加载器
c)调用findclass(string)方法寻找
使用上述步骤如果类被找到且resolve为true,就会去调用resolveclass(class)方法
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;  }}
findclass方法
findclass的源码如下,findclass寻找拥有指定二进制名称的类,jvm鼓励我们重写此方法,需要自定义加载器遵循双亲委托机制,该方法会在检查完父类加载器之后被loadclass方法调用,默认返回classnotfoundexception异常。
protected class<?> findclass(string name) throws classnotfoundexception {    throw new classnotfoundexception(name);}
defineclass方法
defineclass的源码如下,defineclass方法将一个字节数组转换为class的实例。
protected final class<?> defineclass(string name, byte[] b, int off, int len,                                     protectiondomain protectiondomain)    throws classformaterror{    protectiondomain = predefineclass(name, protectiondomain);    string source = defineclasssourcelocation(protectiondomain);    class<?> c = defineclass1(name, b, off, len, protectiondomain, source);    postdefineclass(c, protectiondomain);    return c;}
自定义类加载器
/** * 继承了classloader,这是一个自定义的类加载器 * @author 夜的那种黑丶 */public class classloadertest extends classloader {    public static void main(string[] args) throws exception {        classloadertest loader = new classloadertest("loader");       class<?> clazz = loader.loadclass("classloader.test01");        object object = clazz.newinstance();        system.out.println(object);        system.out.println(object.getclass().getclassloader());    }    //------------------------------以上为测试代码---------------------------------    /**     * 类加载器名称,标识作用     */    private string classloadername;    /**     * 从磁盘读物字节码文件的扩展名     */    private string fileextension = ".class";    /**     * 创建一个类加载器对象,将系统类加载器当做该类加载器的父加载器     * @param classloadername 类加载器名称     */    private classloadertest(string classloadername) {        // 将系统类加载器当做该类加载器的父加载器        super();        this.classloadername = classloadername;    }    /**     * 创建一个类加载器对象,显示指定该类加载器的父加载器     * 前提是需要有一个类加载器作为父加载器     * @param parent 父加载器     * @param classloadername 类加载器名称     */    private classloadertest(classloader parent, string classloadername) {        // 显示指定该类加载器的父加载器        super(parent);        this.classloadername = classloadername;    }    /**     * 寻找拥有指定二进制名称的类,重写classloader类的同名方法,需要自定义加载器遵循双亲委托机制     * 该方法会在检查完父类加载器之后被loadclass方法调用     * 默认返回classnotfoundexception异常     * @param classname 类名     * @return class的实例     * @throws classnotfoundexception 如果类不能被找到,抛出此异常     */    @override    protected class<?> findclass(string classname) throws classnotfoundexception {        byte[] data = this.loadclassdata(classname);        /*         * 通过defineclass方法将字节数组转换为class         * defineclass:将一个字节数组转换为class的实例,在使用这个class之前必须要被解析         */        return this.defineclass(classname, data, 0 , data.length);    }    /**     * io操作,根据类名找到对应文件,返回class文件的二进制信息     * @param classname 类名     * @return class文件的二进制信息     * @throws classnotfoundexception 如果类不能被找到,抛出此异常     */    private byte[] loadclassdata(string classname) throws classnotfoundexception {        inputstream inputstream = null;        byte[] data;        bytearrayoutputstream bytearrayoutputstream = null;        try {            this.classloadername = this.classloadername.replace(".", "/");            inputstream = new fileinputstream(new file(classname + this.fileextension));            bytearrayoutputstream = new bytearrayoutputstream();            int ch;            while (-1 != (ch = inputstream.read())) {                bytearrayoutputstream.write(ch);            }            data = bytearrayoutputstream.tobytearray();        } catch (exception e) {            throw new classnotfoundexception();        } finally {            try {                if (inputstream != null) {                    inputstream.close();                }                if (bytearrayoutputstream != null) {                    bytearrayoutputstream.close();                }            } catch (ioexception e) {                e.printstacktrace();            }        }        return data;    }}
以上是一段自定义类加载器的代码,我们执行这段代码
classloader.test01@7f31245asun.misc.launcher$appclassloader@18b4aac2
可以看见,这段代码中进行类加载的类加载器还是系统类加载器(appclassloader)。这是因为jvm的双亲委托机制造成的,private classloadertest(string classloadername)将系统类加载器当做我们自定义类加载器的父加载器,jvm的双亲委托机制使自定义类加载器委托系统类加载器完成加载。
改造以下代码,添加一个path属性用来指定类加载位置:
public class classloadertest extends classloader {    public static void main(string[] args) throws exception {        classloadertest loader = new classloadertest("loader");        loader.setpath("/home/fanxuan/study/java/jvmstudy/out/production/jvmstudy/");        class<?> clazz = loader.loadclass("classloader.test01");        system.out.println("class:" + clazz);        object object = clazz.newinstance();        system.out.println(object);        system.out.println(object.getclass().getclassloader());    }    //------------------------------以上为测试代码---------------------------------    /**     * 从指定路径加载     */    private string path;    ......        /**     * io操作,根据类名找到对应文件,返回class文件的二进制信息     * @param classname 类名     * @return class文件的二进制信息     * @throws classnotfoundexception 如果类不能被找到,抛出此异常     */    private byte[] loadclassdata(string classname) throws classnotfoundexception {        inputstream inputstream = null;        byte[] data;        bytearrayoutputstream bytearrayoutputstream = null;        classname = classname.replace(".", "/");        try {            this.classloadername = this.classloadername.replace(".", "/");            inputstream = new fileinputstream(new file(this.path + classname + this.fileextension));            bytearrayoutputstream = new bytearrayoutputstream();            int ch;            while (-1 != (ch = inputstream.read())) {                bytearrayoutputstream.write(ch);            }            data = bytearrayoutputstream.tobytearray();        } catch (exception e) {            throw new classnotfoundexception();        } finally {            try {                if (inputstream != null) {                    inputstream.close();                }                if (bytearrayoutputstream != null) {                    bytearrayoutputstream.close();                }            } catch (ioexception e) {                e.printstacktrace();            }        }        return data;    }    public void setpath(string path) {        this.path = path;    }}
运行一下
class:class classloader.test01classloader.test01@7f31245asun.misc.launcher$appclassloader@18b4aac2
修改一下测试代码,并删除工程下的test01.class文件
public static void main(string[] args) throws exception {    classloadertest loader = new classloadertest("loader");   loader.setpath("/home/fanxuan/桌面/");    class<?> clazz = loader.loadclass("classloader.test01");    system.out.println("class:" + clazz);    object object = clazz.newinstance();    system.out.println(object);    system.out.println(object.getclass().getclassloader());}
运行一下
class:class classloader.test01classloader.test01@135fbaa4classloader.classloadertest@7f31245a
分析
改造后的两块代码,第一块代码中加载类的是系统类加载器appclassloader,第二块代码中加载类的是自定义类加载器classloadertest。是因为classloadertest会委托他的父加载器appclassloader加载class,第一块代码的path直接是工程下,appclassloader可以加载到,而第二块代码的path在桌面目录下,所以appclassloader无法加载到,然后classloadertest自身尝试加载并成功加载到。如果第二块代码工程目录下的test01.class文件没有被删除,那么依然是appclassloader加载。
再来测试一块代码
public static void main(string[] args) throws exception {    classloadertest loader = new classloadertest("loader");    loader.setpath("/home/fanxuan/study/java/jvmstudy/out/production/jvmstudy/");    class<?> clazz = loader.loadclass("classloader.test01");    system.out.println("class:" + clazz.hashcode());    object object = clazz.newinstance();    system.out.println(object.getclass().getclassloader());    classloadertest loader2 = new classloadertest("loader");    loader2.setpath("/home/fanxuan/study/java/jvmstudy/out/production/jvmstudy/");    class<?> clazz2 = loader2.loadclass("classloader.test01");    system.out.println("class:" + clazz2.hashcode());    object object2 = clazz2.newinstance();    system.out.println(object2.getclass().getclassloader());}
结果显而易见,类由系统类加载器加载,并且clazz和clazz2是相同的。
class:2133927002sun.misc.launcher$appclassloader@18b4aac2class:2133927002sun.misc.launcher$appclassloader@18b4aac2
再改造一下
public static void main(string[] args) throws exception {    classloadertest loader = new classloadertest("loader");    loader.setpath("/home/fanxuan/桌面/");    class<?> clazz = loader.loadclass("classloader.test01");    system.out.println("class:" + clazz.hashcode());    object object = clazz.newinstance();    system.out.println(object.getclass().getclassloader());    classloadertest loader2 = new classloadertest("loader2");    loader2.setpath("/home/fanxuan/桌面/");    class<?> clazz2 = loader2.loadclass("classloader.test01");    system.out.println("class:" + clazz2.hashcode());    object object2 = clazz2.newinstance();    system.out.println(object2.getclass().getclassloader());}
运行结果
class:325040804classloader.classloadertest@7f31245aclass:621009875classloader.classloadertest@45ee12a7
classloadertest是显而易见,但是clazz和clazz2是不同的,这是因为类加载器的命名空间的原因。
我们可以通过设置父类加载器来让loader和loader2处于同一命名空间
public static void main(string[] args) throws exception {    classloadertest loader = new classloadertest("loader");    loader.setpath("/home/fanxuan/桌面/");    class<?> clazz = loader.loadclass("classloader.test01");    system.out.println("class:" + clazz.hashcode());    object object = clazz.newinstance();    system.out.println(object.getclass().getclassloader());    classloadertest loader2 = new classloadertest(loader, "loader2");    loader2.setpath("/home/fanxuan/桌面/");    class<?> clazz2 = loader2.loadclass("classloader.test01");    system.out.println("class:" + clazz2.hashcode());    object object2 = clazz2.newinstance();    system.out.println(object2.getclass().getclassloader());}
运行结果
class:325040804classloader.classloadertest@7f31245aclass:325040804classloader.classloadertest@7f31245a
扩展:命名空间
1. 每个类加载器都有自己的命名空间,命名空间由该加载器及所有的父加载器所加载的类组成
2. 在同一命名空间中,不会出现类的完整名字(包括类的包名)相同的两个类
3. 在不同的命名空间中,有可能会出现类的完整名字(包括类的包名)相同的两个类
,大量的免费java入门教程,欢迎在线学习!
以上就是java类加载器classloader详解的详细内容。
   
 
   