[java]类加载机制

来源:网页教学基地 时间:2017-01-11 09:02:50  浏览次数:0

双亲委派模型:

三种类加载方式,从BootStrap Loader,Extension Loader,Application Loader。三个管的范围不一样,没一个都会调用父类,用父类的loader加载类。

但是Extension Loader的getParent方法返回为null,因为BootStrap Loader是用C++语言写的,没有指明返回值,所以返回了NULL。


BootStrap Loader属于虚拟机自身的一部分。

Extension Loader,Application Loader是Java语言实现。全部继承自抽象类java.lang.ClassLoader。独立于JVM之外。

三者加载类的加载范围

BootStrap Loader:<Java_Runtime_Home>/lib 

Extension Loader:< Java_Runtime_Home >/lib/ext 或者由系统变量 java.ext.dir 指定位置中的类库加载到内存中

Application Loader负责将系统类路径(CLASSPATH)中指定的类库加载到内存中


为什么要采用这种模型

后面两个类加载器是可以直接调用的。

Java类随着它的类加载器一起具备了一种带有优先级的层次关系。如果没有这个模型,我们不小心定义一个和系统重名的类,那么系统的那个类就不会执行了。在编译期查不到错误,但运行时会报错,可能造成严重后果。


<span style="font-size:14px;">protected synchronized Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// First, check if the class has already been loaded
Class c = findLoadedClass(name);
if (c == null) {
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.
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}</span>

双亲委派模型是通过loadClass()方法来实现的,根据代码以及代码中的注释可以很清楚地了解整个过程其实非常简单:先检查是否已经被加载过,如果没有则调用父加载器的loadClass()方法,如果父加载器为空则默认使用启动类加载器作为父加载器。如果父类加载器加载失败,则先抛出ClassNotFoundException,然后再调用自己的findClass()方法进行加载。

破坏双亲模型

如果想破坏双亲委派模型,则实现ClassLoader类,重写其loadClass方法。

在JAVA中,一个类是否是唯一的,通过类和其加载器来确定。即比较两个类是否相等,只有在这两个类是由同一个类加载器加载的前提之下才有意义,否则,即使这两个类来源于同一个Class类文件,只要加载它的类加载器不相同,那么这两个类必定不相等(这里的相等包括代表类的Class对象的equals()方法、isAssignableFrom()方法、isInstance()方法和instanceof关键字的结果)。


双亲委派模式很好地解决了各个类加载器的基础类统一问题,越基础的类由越上层的类加载器进行加载,但是这个基础类统一有一个不足,当基础类想要调用回下层的用户代码时无法委派子类加载器进行类加载。为了解决这个问题JDK引入了ThreadContext线程上下文,通过线程上下文的setContextClassLoader方法可以设置线程上下文类加载器。


自定义类加载器


<span style="font-size:14px;">public class MyClassLoader extends ClassLoader {

    //类加载器名称
    private String name;
    //加载类的路径
    private String path = "D:/";
    private final String fileType = ".class";
    public MyClassLoader(String name){
        //让系统类加载器成为该 类加载器的父加载器
        super();
        this.name = name;
    }

    public MyClassLoader(ClassLoader parent, String name){
        //显示指定该类加载器的父加载器
        super(parent);
        this.name = name;
    }

    public String getPath() {
        return path;
    }

    public void setPath(String path) {
        this.path = path;
    }

    @Override
    public String toString() {
        return this.name;
    }

    /**
     * 获取.class文件的字节数组————》获取资源
     * @param name
     * @return
     */
    private byte[] loaderClassData(String name){
        InputStream is = null;
        byte[] data = null;
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        this.name = this.name.replace(".", "/");
        try {
            is = new FileInputStream(new File(path + name + fileType));
            int c = 0;
            while(-1 != (c = is.read())){
                baos.write(c);
            }
            data = baos.toByteArray();

        } catch (Exception e) {
            e.printStackTrace();
        } finally{
            try {
                is.close();
                baos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return data;
    }

    /**
     * 获取Class对象——————》解析数据
     */
    @Override
    public Class<?> findClass(String name){
        byte[] data = loaderClassData(name);
        return this.defineClass(name, data, 0, data.length);
    }

    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        //loader1的父加载器为系统类加载器
        MyClassLoader loader1 = new MyClassLoader("loader1");
        loader1.setPath("D:/lib1/");
        //loader2的父加载器为loader1
        MyClassLoader loader2 = new MyClassLoader(loader1, "loader2");
        loader2.setPath("D:/lib2/");
        //loader3的父加载器为根类加载器
        MyClassLoader loader3 = new MyClassLoader(null, "loader3");
        loader3.setPath("D:/lib3/");

        Class clazz = loader2.loadClass("Sample");
        Object object = clazz.newInstance();
    }
}</span>


<span style="font-size:14px;">public class Sample {

    public Sample(){
        System.out.println("Sample is loaded by " + this.getClass().getClassLoader());
        new A();
    }
}
public class A {

    public A(){
        System.out.println("A is loaded by " + this.getClass().getClassLoader());
    }
}</span>

当执行loader2.loaderClass("Sample")时,先由它上层的所有父加载器尝试加载Sample类。loader1从D:/lib1/目录下成功的加载了Sample类,因此laoder1是Sample类的定义类加载器,loader1和loader2是Sample类的初始类加载器。
当执行loader3.loadClass("Sample")时,先由它上层的所有父加载器尝试加载Sample类。loader3的父加载器为根类加载器,它无法加载Sample类,接着loader3从D:/lib3/目录下成功地加载了Sample类,因此loader3是Sample类的定义类加载器即初始类加载器。
在Sample类中主动使用了A类,当执行Sample类的构造方法中的new A()语句时,Java虚拟机需要先加载Dog类,Java虚拟机会勇Sample类的定义类加载器去加载Dog类,加载过程也同样采用父亲委托机制。

这部分引用自:http://www.itzhai.com/java-virtual-machine-notes-custom-class-loader-implementation-and-use-of.html






最近相关