分析JVM双亲委派模型的类加载源码 自定义类加载器

双亲委派模型下,在父类加载器无法加载的情况下再由当前类加载器去加载。具体的实现逻辑在java.util.ClassLoader抽象类的loadClass方法中。在该方法中,先检查是否已经加载过,如果没有,就让父类加载器加载。如果父类加载器服务加载,就调用findClass方法加载。findClass方法是空的,需要用户重载它。所以,按照这样的逻辑,我们在使用loadClass之前需要重载findClass方法。

	Boostrap ClassLoader
		|
	Extension ClassLoader
		|
	Application ClassLoader
	|		|
OtherClassLoader1   OtherClassLoader2 ...
  • loadClass
public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
}
  • loadClass(String name, boolean resolve):
protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        // 1. 检查是否已经加载过
        Class c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                if (parent != null) {
                	//当前加载器存在父类加载器,递归调用父类加载器
                    c = parent.loadClass(name, false);
                } else {
                	//检查该类是否被Bootstrap加载器加载过,有则返回,否则返回null
                    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) {
        	//链接link所加载的类。该方法名起得不好,有误导性
            resolveClass(c);
        }
        return c;
    }
}
  • find Class
    findClass的默认实现如下:
protected Class<?> findClass(String name) throws ClassNotFoundException {
	//如果没有重载,一调用就抛出异常
        throw new ClassNotFoundException(name); 
}

findClass的返回值是Class类型,而我们可以使用defineClass方法将一个byte[]转换成Class类型。而byte[]数据可以通过文件读取class字节码文件获取。

  • defineClass
    defineClass定义和默认实现如下:
protected final Class<?> defineClass(String name, byte[] b, int off, int len) throws ClassFormatError  {
        return defineClass(name, b, off, len, null);
}

简单示例

首先,我们定义一个待加载的普通Java类:Student.java, 其中package为空,可以在任何路径下编译该文件成class字节码文件。

public class Student {
    public void say(){
        System.out.println("hello world");
    }
}

进入window的cmd命令行窗口,进入Student.java所在目录,使用命令javac Student.java获得Student.class文件。
在这里插入图片描述

  • 加载Student.class文件
    接下来就是自定义我们的类加载器。阅读下面的代码,细心的读者会发现,findClass方法是重载的方法,不重载的话无法运行。下面这段代码可以在电脑任何位置执行javac Test.javajava Test来进行测试。
mport java.lang.reflect.Method;
import java.io.FileInputStream;

class MyClassLoader extends ClassLoader {

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            byte[] data = loadByte();
            //将byte[]数据转换成Class类型的对象
            return defineClass(name, data, 0, data.length);
        } catch (Exception e) {
            e.printStackTrace();
            throw new ClassNotFoundException();
        }
    }

	//从硬盘中读取class字节码文件,并转换成byte[]数据
    private byte[] loadByte() throws Exception {
        String name = "C:/Users/USER/Desktop/temp/Student.class";
        FileInputStream fis = new FileInputStream(name);
        int len = fis.available();
        byte[] data = new byte[len];
        fis.read(data);
        fis.close();
        return data;

    }
};

public class Test {

    public static void main(String args[]) throws Exception {
        MyClassLoader classLoader = new MyClassLoader();
        //加载Student的Class类型对象
        Class clazz = classLoader.loadClass("Student");
        Object obj = clazz.newInstance();
        //使用反射的方式获取和执行say()方法
        Method helloMethod = clazz.getDeclaredMethod("say", null);
        helloMethod.invoke(obj, null);
    }
}

谢谢!

猜你喜欢

转载自blog.csdn.net/liangyihuai/article/details/85040545