java · 2019-08-10 0

Java类加载器及自定义类加载器

一、类加载器父亲委托机制

Java类加载器采用父亲委托机制,先尝试用父加载器加载,父加载器无法加载,则此加载器加载,

注意,此父加载器与子加载器并非继承关系,而是指定的关系。

从ClassLoader类loadClass()方法的源码,可以看出父亲委托机制的思想的实现。

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;
    }
}

二、类加载器分类

(1)根(Bootstrap)类加载器:根类加载器的实现依赖于底层操作系统,并没有继承java.lang.ClassLoader类(加载虚拟机的核心类库,如java.lang.Object,根类加载器从系统属性sun.boot.class.path所指定的目录中加载类库)


import java.util.Properties;

public class TestCP{
    //命令行下运行
    public static void main(String[] args){
        Properties pros = System.getProperties();
        String p1= pros.getProperty("sun.boot.class.path");
        for (String s : p1.split(";")){
            System.out.println(s);
        }
    }
}

结果:

(2)扩展(Extension)类加载器:它的父加载器为根类加载器,纯Java类,是java.lang.ClassLoader的子类(加载系统属性java.ext.dirs所指定的目录下的类库,或从JDK安装目录下的jre/lib/ext子目录下加载类库)


import java.util.Properties;

public class TestCP{
    //命令行下运行
    public static void main(String[] args){
        Properties pros = System.getProperties();
        String p1= pros.getProperty("java.ext.dirs");
        for (String s : p1.split(";")){
            System.out.println(s);
        }
    }
}

结果 :

(3)系统(System)类加载器:它的父加载器为扩展类加载器,纯Java类,是java.lang.ClassLoader的子类(加载系统属性java.class.path所指定的目录下的类库,或从环境变量classpath所指定的目录下的类库)


import java.util.Properties;

public class TestCP{
    //命令行下运行
    public static void main(String[] args){
        Properties pros = System.getProperties();
        String p1= pros.getProperty("java.class.path");
        for (String s : p1.split(";")){
            System.out.println(s);
        }
    }
}

结果:

在集成编辑代码工具,引入第三方包,实际上就是把第三方包的路径加到java.class.path中,使用时用系统类加载器加载

查看加载Object、JarFileSystemProvider、TestClassLoader的加载器:

import com.sun.nio.zipfs.JarFileSystemProvider;

public class TestClassLoader {

    public static void main(String[] args) throws ClassNotFoundException {
        ClassLoader objClassLoader = Object.class.getClassLoader();
        System.out.println(objClassLoader);

        ClassLoader proClassLoader = JarFileSystemProvider.class.getClassLoader();
        System.out.println(proClassLoader);

        ClassLoader testClassLoaderClassLoader = TestClassLoader.class.getClassLoader();
        System.out.println(testClassLoaderClassLoader);
    }
}

查看加载器结果:

Object类由根加载器加载;com.sun.nio.zipfs.JarFileSystemProvider在ext的目录下,由扩展加载器加载;TestClassLoader在java.class.path值中,由系统加载器加载

命令空间:每个类加载器都有自己的命名空间,命名空间由该加载器及所有父加载器所加载的类组成。

运行时包:由同一个类加载器加载的属于相同包的类组成了运行时包。只有属于同一个运行时包的类才能相互访问包可见(即默认访问级别)的类和类成员,这样的限制能避免用户自定义的类冒充核心类库的类,去访问核心类库的包可见成员。

不同类加载器的命名空间存在以下关系:

同一个命名空间的类是可见的;子加载器的命名空间包含父加载器的命名空间,因此由子加载器加载的类可以看见父加载器加载的类;父加载器的类看不见子加载器的类;如果两个加载器之间没有直接或间接的父子关系,那么它们格各自加载的类是相互看不见的(可采用Java反射机制来访问对方实例的属性和方法)。

三、自定义加载器

MyClassLoader类:

每个类加载器:loadClass->findClass->defineClass

自定义加载器需要,继承ClassLoader;重写findClass(String name)方法;调用defineClass()方法

package classloader;

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

public class MyClassLoader extends ClassLoader {

    private String name;
    private String path;
    private final String fileType = ".class";

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

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

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] data = loadClassData(name);
        return defineClass(name, data, 0, data.length);
    }

    /*把类的二进制数据读到内存中*/
    private byte[] loadClassData(String name) throws ClassNotFoundException {
        FileInputStream fis = null;
        byte[] data = null;
        ByteArrayOutputStream baos = null;
        try {
            name = name.replace(".", "\");
            fis = new FileInputStream(new File(path + name + fileType));
            baos = new ByteArrayOutputStream();
            int ch = 0;
            while ((ch = fis.read()) != -1){
                baos.write(ch);
            }
            data = baos.toByteArray();
        }catch (IOException e){
            throw new ClassNotFoundException("Class is not found:" + name, e);
        }finally {
            if(fis != null){
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (baos != null){
                try {
                    baos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return data;
    }

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

    public String getPath() {
        return path;
    }

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

被自定义加载的Dog类:

打印出加载Dog类的加载器名称

package classloader;

public class Dog {

    public Dog(){
        System.out.println("Dog is loaded by " + this.getClass().getClassLoader());
    }

}

Test类:

设置loader1加载的路径为F:libserverlib,loader2加载的路径为F:libclientlib,loader3加载的路径为F:libotherlib,

把Dog类的.class文件复制到三个加载器的加载路径下(Dog在classloader包下,需要在加载路径下建立classloader文件夹,再把Dog类的.class文件复制到classloader目录下)

Test类和MyClassLoader的.class文件下,复制到F:libsyslib下

loader1加载器的父加载器是系统类加载器;loader2加载器的父加载器是loaer1;loader3加载器的父加载器是根类加载器

public class Test {

    public static void main(String[] args) throws Exception {
        MyClassLoader loader1 = new MyClassLoader("loader1");
        loader1.setPath("F:\lib\serverlib\");
        MyClassLoader loader2 = new MyClassLoader(loader1, "loader2");
        loader2.setPath("F:\lib\clientlib\");
        MyClassLoader loader3 = new MyClassLoader(null, "loader3");
        loader3.setPath("F:\lib\otherlib\");
        test(loader2);
        test(loader3);
    }

    public static void test(ClassLoader loader) throws Exception {
        Class objClass = loader.loadClass("classloader.Dog");
        Object obj = objClass.newInstance();
    }
}

加载器结构图:

执行java -classpath F:libsyslib classloader.Test

指定classpath为F:libsyslib,则此目录下由系统加载器加载