ClassLoader

来源:互联网 发布:专业视频录制软件 编辑:程序博客网 时间:2024/06/05 16:48

ClassLoader,即java类加载器,主要作用是将class加载到JVM内,同时它还要考虑class由谁来加载。在说java的类加载机制之前,还是像前面的博客一样,先说说为什么要知道java的类加载机制。个人认为主要有以下几个原因:

按需加载。JVM启动时不能确定我要加载哪些东西,或者有些类非常大,我只希望用到它时再加载,并非一次性加载所有的class,所以这时候了解了加载机制就可以按需加载了。
类隔离。比如web容器中部署多个应用,应用之间互相可能会有冲突,所以希望尽量隔离,这里可能就要分析各个应用加载的资源和加载顺序之间的冲突,针对这些冲突再自己定些规则,让它们能够愉快地玩耍。
资源回收。如果你不了解java是如何加载资源的,又怎么理解java是如何回收资源的?

一般说到java的类加载机制,都要说到“双亲委派模型”(其实个人很不理解为什么叫“双亲”,其实英文叫“parent”)。使用这种机制,可以避免重复加载,当父亲已经加载了该类的时候,就没有必要子ClassLoader再加载一次。JVM根据 类名+包名+ClassLoader实例ID 来判定两个类是否相同,是否已经加载过(所以这里可以略微扩展下,可以通过创建不同的classloader实例来实现类的热部署)。
有个图很形象(来源见参考资料)。

这里写图片描述

下面结合上图来详细介绍下java的类加载过程。

BootStrapClassLoader。它是最顶层的类加载器,是由C++编写而成, 已经内嵌到JVM中了。在JVM启动时会初始化该ClassLoader,它主要用来读取Java的核心类库JRE/lib/rt.jar中所有的class文件,这个jar文件中包含了java规范定义的所有接口及实现。
ExtensionClassLoader。它是用来读取Java的一些扩展类库,如读取JRE/lib/ext/*.jar中的包等(这里要注意,有些版本的是没有ext这个目录的)。
AppClassLoader。它是用来读取CLASSPATH下指定的所有jar包或目录的类文件,一般情况下这个就是程序中默认的类加载器。
CustomClassLoader。它是用户自定义编写的,它用来读取指定类文件 。基于自定义的ClassLoader可用于加载非Classpath中(如从网络上下载的jar或二进制)的jar及目录、还可以在加载前对class文件优一些动作,如解密、编码等。
很多资料和文章里说,ExtClassLoader的父类加载器是BootStrapClassLoader,其实这里省掉了一句话,容易造成很多新手(比如我)的迷惑。严格来说,ExtClassLoader的父类加载器是null,只不过在默认的ClassLoader 的 loadClass 方法中,当parent为null时,是交给BootStrapClassLoader来处理的,而且ExtClassLoader 没有重写默认的loadClass方法,所以,ExtClassLoader也会调用BootStrapLoader类加载器来加载,这就导致“BootStrapClassLoader具备了ExtClassLoader父类加载器的功能”。看一下下面的代码就很容易理解上面这一大段话了。

三个重要的方法

查看classloader的源码可以发现三个重要的方法:

loadClass。classloader加载类的入口,此方法负责加载指定名字的类,ClassLoader的实现方法为先从已经加载的类中寻找,如没有则继续从父ClassLoader中寻找,如仍然没找到,则从BootstrapClassLoader中寻找,最后再调用findClass方法来寻找,如要改变类的加载顺序,则可覆盖此方法,如加载顺序相同,则可通过覆盖findClass来做特殊的处理,例如解密、固定路径寻找等,当通过整个寻找类的过程仍然未获取到Class对象时,则抛出ClassNotFoundException。如类需要resolve,则调用resolveClass进行链接。
findClass。此方法直接抛出ClassNotFoundException,因此需要通过覆盖loadClass或此方法来以自定义的方式加载相应的类。
defineClass。此方法负责将二进制的字节码转换为Class对象,这个方法对于自定义加载类而言非常重要,如二进制的字节码的格式不符合JVM Class文件的格式,抛出ClassFormatError;如需要生成的类名和二进制字节码中的不同,则抛出NoClassDefFoundError;如需要加载的class是受保护的、采用不同签名的或类名是以java.开头的,则抛出SecurityException;如需加载的class在此ClassLoader中已加载,则抛出LinkageError。

可以看到,悲剧的报错了。根据class loader命名空间规则,每个class loader都有自己唯一的命名空间,每个class loader 只能访问自己命名空间中的类,如果一个类是委托parent加载的,那么加载后,这个类就类似共享的,parent和child都可以访问到这个类,因为parent是不会委托child加载类的,所以child加载的类parent访问不到。简单来说,即子加载器的命名空间包含了parent加载的所有类,反过来则不成立。 本例中LoaderTest类是AppClassLoader加载的,所以其看不见由MyClassLoader加载的HighRichHandsome类,但Person接口是可以访问的,所以赋给Person类型不会出错。

package com.zhy.concurrency.semaphore;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.InputStream;public class MyClassLoader extends ClassLoader{    /*      * 覆盖了父类的findClass,实现自定义的classloader     */    @Override    public Class<?> findClass(String name) {        byte[] bt = loadClassData(name);        return defineClass(name, bt, 0, bt.length);    }    private byte[] loadClassData(String className) {        InputStream is = getClass().getClassLoader().getResourceAsStream(                className.replace(".", "/") + ".class");        ByteArrayOutputStream byteSt = new ByteArrayOutputStream();        int len = 0;        try {            while ((len = is.read()) != -1) {                byteSt.write(len);            }        } catch (IOException e) {            e.printStackTrace();        }        return byteSt.toByteArray();    }    public static void main(String[] args)throws Exception {            MyClassLoader loader = new MyClassLoader();            Class<HighRichHandsome> c = (Class<HighRichHandsome>) loader.findClass("com.zhy.concurrency.semaphore.HighRichHandsome");            System.out.println("Loaded by :" + c.getClassLoader());            Person p = (Person) c.newInstance();            p.say();            System.out.println(c.getClassLoader().getClass().getName());            System.out.println(HighRichHandsome.class.getClassLoader().getClass().getName());            HighRichHandsome man = (HighRichHandsome) c.newInstance();            man.say();                }}
Loaded by :com.zhy.concurrency.semaphore.MyClassLoader@6d06d69cI don't care whether you are rich or notcom.zhy.concurrency.semaphore.MyClassLoadersun.misc.Launcher$AppClassLoaderException in thread "main" java.lang.ClassCastException: com.zhy.concurrency.semaphore.HighRichHandsome cannot be cast to com.zhy.concurrency.semaphore.HighRichHandsome    at com.zhy.concurrency.semaphore.MyClassLoader.main(MyClassLoader.java:41)
0 0
原创粉丝点击