日常记录——JVM—自定义类加载器

一、简介

自定义类加载器实现方式为,继承ClassLoader类,重写其内部方法。简单介绍一下ClassLoader的与加载类相关的方法:
1.getParent():返回该类加载器的父类加载器。
2.loadClass(String name):加载名称为 name 的类,返回的结果是 java.lang.Class 类的实例。
3.findClass(String name):查找名称为 name 的类,返回的结果是 java.lang.Class 类的实例。
4.findLoadedClass(String name):查找名称为 name 的已经被加载过的类,返回的结果是 java.lang.Class 类的实例。
5.defineClass(String name, byte[] b, int off, int len):把字节数组 b 中的内容转换成 Java 类,返回的结果是 java.lang.Class 类的实例。这个方法被声明为 final 的。
6.resolveClass(Class<?> c):解析指定的 Java 类。
还有一个ClassLoader类型的parent成员变量,存储当前加载器的父加载器。

二、loadClass方法源码解析

loadClass方法实现了双亲委派的概念。

	protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
    	//加锁
        synchronized (getClassLoadingLock(name)) {
            // 首先,当前加载器检查该类是否已经加载  加载就返回
            Class<?> c = findLoadedClass(name);
            if (c == null) {
            	//类加载器为了记录统计数据
                long t0 = System.nanoTime();
                try {
                	//如果检查自己缓存没有,则找父加载器也执行loadClass方法  这里是递归  
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                    	//返回由引导类加载器加载的指定类 如果找不到则返回null。
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }
				//如果仍然找不到,按由父到子调用findClass
                if (c == null) {
                    long t1 = System.nanoTime();
                    //查找名称为 name 的类,返回的结果是 java.lang.Class 类的实例  找不到抛出 ClassNotFoundException  整个方法都是在递归
                    c = findClass(name);
                    //类加载器为了记录统计数据
                    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.自定义一个遵循双亲委派的类加载器:继承ClassLoader类,重写其中当前加载器的findClass方法即可,实现代码如下:

public class FileSystemClassLoader extends ClassLoader {
	//class文件位置
    private static final String ROOTDIR = "D:/ideaworker/out/production/ideaworker/";
	//重写findClass方法
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] classData = getClassData(name);
        if (classData == null) {
            throw new ClassNotFoundException();
        }
        else {
        	//将字节文件转换为Class对象
            return defineClass(name, classData, 0, classData.length);
        }
    }
	//读取字节文件
    private byte[] getClassData(String className) {
        String path = classNameToPath(className);
        try {
            InputStream ins = new FileInputStream(path);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int bufferSize = 4096;
            byte[] buffer = new byte[bufferSize];
            int bytesNumRead = 0;
            while ((bytesNumRead = ins.read(buffer)) != -1) {
                baos.write(buffer, 0, bytesNumRead);
            }
            return baos.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
	//路径拼接转换
    private String classNameToPath(String className) {
        return ROOTDIR + File.separatorChar
                + className.replace('.', File.separatorChar) + ".class";
    }
	//测试两次加载类返回class对象是否为同一对象
    public static void main(String[] args) throws Exception {
        FileSystemClassLoader fileSystemClassLoader = new FileSystemClassLoader();
        Class clazz1 = fileSystemClassLoader.loadClass("com.company.jvm.test");
        fileSystemClassLoader = new FileSystemClassLoader();
        Class clazz2 = fileSystemClassLoader.loadClass("com.company.jvm.test");
        System.out.println(clazz1 == clazz2);
    }
}

输出结果为true,因为该类已经被加载过,遵循双亲委派原理,不会重复加载。
2.自定义一个不遵循双亲委派的类加载器:因为双亲委派原理实现在loadClass方法内,所以重写loadClass方法,不去检查父加载器的加载情况。实现代码如下:

public class MyClassLoader extends ClassLoader {
    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException{
        File f = new File("D:/ideaworker/out/production/ideaworker/" + name.replace(".","/").concat(".class"));
        if(!f.exists()){
            return super.loadClass(name);
        }
        try {
            InputStream is = new FileInputStream(f);
            byte[] b = new byte[is.available()];
            is.read(b);
            return defineClass(name,b,0,b.length);
        }catch (IOException e){
            e.printStackTrace();
        }
        return super.loadClass(name);
    }
	//测试两次加载类返回class对象是否为同一对象
    public static void main(String[] args) throws Exception {
        MyClassLoader myClassLoader = new MyClassLoader();
        Class clazz1 = myClassLoader.loadClass("com.company.jvm.test");
        myClassLoader = new MyClassLoader();
        Class clazz2 = myClassLoader.loadClass("com.company.jvm.test");
        System.out.println(clazz1 == clazz2);
    }
}

输出结果为false,因为不遵循双亲委派原理,不去递归查找父类是否加载过,只要调用就加载一次,所以两次加载返回对象不一致。
打破双亲委派的情况:热加载(tomcat,osgi相同类库包,版本不同)、线程上下文类加载器(线程运行期间自定义类加载器,默认应用程序类加载器Application ClassLoader, getContextClassLoader() 和 setContextClassLoader(ClassLoader cl) 用来获取和设置线程的上下文类加载器)。

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页