jvm - ClassLoader

类加载机制

  类加载器用来把类加载到Java虚拟机中,从JDK1.2开始,类的加在过程采用父亲委托机制。采用这种方式的祝要原因是保证Java平台的安全。在这种机制下,除了Jvm自带的根类加载器以外,其余的类加载器都有且只有一个父类加载器。

JVM自带的几种类加载器

  • 根类加载器(Bootstrap):该加载器没有父加载器。它负责加载虚拟机的核心类库,如java.lang.*等。可以看出,java.lang.Object就是由根类加载器加载的。根类加载器从系统属性sun.boot.class.path所指定的目录中加载类库。它的实现依赖于底层操作系统。
  • 扩展类加载器(Extension):它的父加载器为根类加载器。它从java.ext.dirs系统属性指定的目录中加在类库,或者从JDK的安装目录的jre\lib\ext子目录(扩展目录)下加在类库,如果用户创建的JAR文件放在这个目录下,也会自动由扩展类加载器加载。扩展类加载器是纯Java类,是java.lang.ClassLoader类的子类。
  • 系统类加载器(System):也称为应用类加载器,它的父加载器为扩展类加载器。它从环境变量classpath或者系统属性java.class.path所指定的目录中加载类,它是用户自定义的类加载器的默认父加载器。它也是纯Java类,是java.lang.ClassLoader类的子类。

用户自定义类加载器

  除了系统自带的类加载器,用户可以自定义类加载器,继承抽象类java.lang.ClassLoader。
  这里写图片描述
  需要注意的是:类加载器之间的父子关系,实际上是加载器之间的包装关系,并不是类之间的继承关系。一对父子加载器可能是一个类加载器的两个实例,也可能不是。

父委托机制

  什么是父委托机制?简单来说,当一个类加载器加载一个类时,首先它会查找在自己的命名空间(这个概念下面会讲到)下,该类是否已经加载,如果没有,它会委托给其父加载器加载,它的父亲又会继续向其父父加载器委托,以此类推。当某个父加载器无法加载这个类时,那么就由当前的加载器加载,然后依次把结果返回,最终加载这个类的加载器,我们称为定义类加载器,所有能成功返回Class对象引用的类加载器(包括定义类加载器)都被称为初始类加载器。显然定义类加载器和它的子加载器都是初始类加载器。
  举个例子:
    假设有loader1和loader2两个类加载器,loader1为loader2的父加载器。当你使用loader2加载一个类(Test)时,loader2会先检查Test类是否已经加载,如果没有加载,它会委托给loader1加载。loader1又会委托给系统类加载器(默认的自定义类加载器的父加载器)加载。假如系统类加载器加载不了,就由loader1加载器加载。如果loader1加载成功,那么就将结果返回。此时loader1被称为定义类加载器,loader1和loader2被称为初始类加载器。

命名空间

  每个类加载器都有自己的命名空间,命名空间由该类加载器及所有父加载器所加载的类组成。在同一个命名空间中,不会出现类的完整名字(包括类的包名)相同的两个类;在不同的命名空间中,又可能会出现类的完整名字相同的两个类。
  同一个命名空间内的类是相互可见的。子类加载器的命名空间包含所有父类加载器的命名空间。因此由子类加载器加载的类,可以看到父类加载器加载的类。例如系统类加载器加载的类,可以看到根类加载器加载的类。但是由父类加载器加载的类,不能看见子类加载器加载的类。
  如果两个加载器之间没有直接或者间接的父子关系,那么他们各自加载的类相互不可见。

运行时包

  由同一个类加载器加载的属于相同包的类组成了运行时包。决定两个类是不是属于同一个运行时包,不仅要看它们的包名是否相同,还要看定义类加载器是否相同。只有属于同一个运行时包的类才能互相访问。也即是包内可见(类的默认访问级别)。这样能够限制用户自定义类冒充核心类库的类,去访问核心类库的包可见成员。

自定义类加载器

  这里我们写一段测试代码,来测试我们上面所讲的内容:

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

/**
 * Created by chengli on 2017/1/2.
 */
public class MyClassLoader extends ClassLoader {
    //类加载器的名称
    private String name;
    //类存放的路径
    private String path = "D:/classloader";

    MyClassLoader(String name) {
        this.name = name;
    }

    MyClassLoader(ClassLoader parent, String name) {
        super(parent);
        this.name = name;
    }

    /**
     * 重写findClass方法
     */
    @Override
    public Class<?> findClass(String name) {
        byte[] data = loadClassData(name);
        if (data == null) {
            return null;
        }
        return this.defineClass(name, data, 0, data.length);
    }

    public byte[] loadClassData(String name) {
        FileInputStream is = null;
        try {
            name = name.replace(".", "/");
            File file = new File(String.format("%s/%s%s", path, name, ".class"));
            if (!file.exists()) {
                return null;
            }
            is = new FileInputStream(file);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int b;
            while ((b = is.read()) != -1) {
                baos.write(b);
            }
            return baos.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
    }

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

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

    public static void main(String[] args) throws Exception {
        //新建一个类加载器
        MyClassLoader parent = new MyClassLoader("parent");
        parent.setPath("d:/classloader/parent");
        MyClassLoader child = new MyClassLoader(parent, "child");
        child.setPath("d:/classloader/child");
        //加载类,得到Class对象,这里使用child类加载器来加载
        Class<?> clazz = child.loadClass("Example");
        //得到类的实例
        clazz.newInstance();
    }
}
/**
 * Created by chengli on 2017/1/2.
 */
public class Example {
    public Example() {
        System.out.println("Example class loaded by :" + this.getClass().getClassLoader());
    }
}

  我们创建了两个类加载器,parent加载d:/classloader/parent下的类,child加载d:/classloader/child的类。类放置路径结构如下:
  这里写图片描述
  
运行main方法结果如下:
  这里写图片描述
可见目前使用的是系统类加载器,加载的是main文件夹下的Example。
  现在我们把main文件夹下的Example类删除:
  这里写图片描述
  
再运行:

  这里写图片描述
  现在把parent目录下的Example也删除:
  这里写图片描述
运行结果:
  这里写图片描述
从以上实例,我们可以看到类加载的父委托机制是怎么运行的。

  现在我们把程序改一下,并增加一个Dog类:

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

/**
 * Created by chengli on 2017/1/2.
 */
public class MyClassLoader extends ClassLoader {
    //类加载器的名称
    private String name;
    //类存放的路径
    private String path = "D:/classloader";

    MyClassLoader(String name) {
        this.name = name;
    }

    MyClassLoader(ClassLoader parent, String name) {
        super(parent);
        this.name = name;
    }

    /**
     * 重写findClass方法
     */
    @Override
    public Class<?> findClass(String name) {
        byte[] data = loadClassData(name);
        if (data == null) {
            return null;
        }
        return this.defineClass(name, data, 0, data.length);
    }

    public byte[] loadClassData(String name) {
        FileInputStream is = null;
        try {
            name = name.replace(".", "/");
            File file = new File(String.format("%s/%s%s", path, name, ".class"));
            if (!file.exists()) {
                return null;
            }
            is = new FileInputStream(file);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int b;
            while ((b = is.read()) != -1) {
                baos.write(b);
            }
            return baos.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
    }

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

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

    public static void main(String[] args) throws Exception {
        //新建一个类加载器
        MyClassLoader parent = new MyClassLoader("parent");
        parent.setPath("d:/classloader/parent");
        MyClassLoader child = new MyClassLoader(parent, "child");
        child.setPath("d:/classloader/child");
        /**改动的地方在这里*/
        //加载类,得到Class对象
        child.loadClass("Dog");
        Class<?> clazz = parent.loadClass("Example");
        //得到类的实例
        clazz.newInstance();
    }
}
/**
 * Created by chengli on 2017/1/2.
 */
public class Dog {
    public Dog() {
        System.out.println("this Dog loaded by :" + this.getClass().getClassLoader());
    }
}
/**
 * Created by chengli on 2017/1/2.
 */
public class Example {
    public Example() {
        System.out.println("Example class loaded by :" + this.getClass().getClassLoader());
        new Dog();
    }
}

这里我让父加载器加载Example,子加载器加载Dog,但是在Example有引用Dog类。
结构如下:
  这里写图片描述
此时运行会发现有异常:
  这里写图片描述
此时我们印证了上面的一句话:
  同一个命名空间内的类是相互可见的。子类加载器的命名空间包含所有父类加载器的命名空间。因此由子类加载器加载的类,可以看到父类加载器加载的类。例如系统类加载器加载的类可以看到根类加载器加载的类。但是由父类加载器加载的类不能看见子类加载器加载的类
反过来,让child加载Example,parent加载Dog是可以运行正常的:

public class MyClassLoader extends ClassLoader {
    …
    …
    …
    public static void main(String[] args) throws Exception {
    …
    …
    …
    /**篇幅原因这里至贴出改动的地方*/
        Class<?> clazz = child.loadClass("Example");
        parent.loadClass("Dog");
        //得到类的实例
        clazz.newInstance();
    }
}

代码目录结构:
  这里写图片描述
运行结果:
  这里写图片描述

扫描二维码关注公众号,回复: 2869971 查看本文章

猜你喜欢

转载自blog.csdn.net/lchpersonal521/article/details/53979257