类加载十:来写一个自定义类加载器

十、来写一个自定义类加载器

在写代码之前,先看一下类加载loadClass的源码:

Class<?> clazz = loader1.loadClass("com.lxl.jvm.MyTest01");

还是调用自己的loadClass,第二个参数resolve :If <tt>true</tt> then resolve the class

    public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }

最终的调用:

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

其实很好理解,先对加载同一个类进行同步,再看是否已经加载过这个类findLoadedClass。

如果加载过就返回类,如果resolve为true,就调用native方法进行resolveClass,这个就暂时忽略。

如果没有加载过类,就会调用父加载器的loadClass,也就是第八篇文章中的双亲委托模型。继续加载。

一直到父加载器为根加载器为止,才开始调用findClass进行类加载。

调用父加载器的loadClass后,如果还没加载到就自己加载,就是有父加载器,父加载器加载。父加载器不行,再自己上~就是这么简单。

另外重点说的是,父加载器和父类不是同一个概念!!!自己甚至可以成为自己的父加载器,没有类的继承关系!

***这个findClass也是我们这回的重点,我们自定义类加载器重新的就是这个方法。代码如下

public class MyTest16 extends ClassLoader{

	private String classLoaderName;
	private final String fileExtension = ".class";
	private final String filePath = "C:\\Users\\l8934\\Desktop\\";
	
	public MyTest16(String classLoaderName) {
		super(); // 将系统类加载器当做该类加载器的父加载器
		this.classLoaderName = classLoaderName;
	}
	
	public MyTest16(ClassLoader parent ,String classLoaderName) {
		super(parent); // 显式指定该类加载器的父加载器
		this.classLoaderName = classLoaderName;
	}

	@Override
	public String toString() {
		return "Mytest16 [classLoaderName=" + classLoaderName + "]";
	}
	
	/**
     * 这是查找非classpath下的类(自定义目录),并未打破双亲规则
     * @param name
     * @return
     * @throws ClassNotFoundException
     */
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] classBytes = this.loadClassData(name);//通过class文件获取数组
        if(null == classBytes || classBytes.length == 0)
            throw new ClassNotFoundException("load this class is failed");

        //这样还是加载不到java.*的类,因为在ClassLoader里面是写死的
        Class c = AccessController.doPrivileged(new PrivilegedAction<Class>() {
            @Override
            public Class run() {
                return defineClass(name, classBytes, 0, classBytes.length);
            }
        });

        System.out.println("find class successfully");
        return c;
    }
	
	private byte[] loadClassData(String name) {
		InputStream is = null;
		byte[] data = null;
		ByteArrayOutputStream baos = null;
		
		try {
			name = filePath + name.replace(".", "//") + fileExtension;
			
			is = new FileInputStream(new File(name));
			baos = new ByteArrayOutputStream();
			int ch = 0;
			while(-1 != (ch = is.read())) {
				baos.write(ch);
			}
			data = baos.toByteArray();
			
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				baos.close();
				is.close();
			} catch (Exception e2) {
				e2.printStackTrace();
			}
		}
		return data;
	}
	
	public static void main(String[] args) throws Exception {
		MyTest16 loader1 = new MyTest16("load1");
		
		Class<?> clazz = loader1.loadClass("com.lxl.jvm.MyTest01");
		Object object = clazz.newInstance();
		System.out.println(object);
		System.out.println(object.getClass().getClassLoader());
		
		MyTest16 loader2 = new MyTest16("load2");
		Class<?> clazz2 = loader2.loadClass("com.lxl.jvm.MyTest01");
		Object object2 = clazz2.newInstance();
		System.out.println(object2);
		System.out.println(object2.getClass().getClassLoader());
	}
}

这里就是我们的自定义类加载器了,我们重写了findClass方法,findClass方法中,调用了我们的loadClassData方法,将class文件转为byte数组返回。不难理解,我们定义了filePath,路径为桌面。我们运行一下~~

结果为:

com.lxl.jvm.MyTest01@15db9742
sun.misc.Launcher$AppClassLoader@73d16e93
com.lxl.jvm.MyTest01@6d06d69c
sun.misc.Launcher$AppClassLoader@73d16e93

0.0~~~~~~~~~~~~~~~~~不错,让你们失望了,依然是应用类加载器!!!原因很简单了,双亲模型帮我们把类给加载了么~

至于为啥,那就是我们idea会将我们工作空间地址配置给应用类加载器,所以我们的代码都是应用类加载器完成的!

解决这个问题有两个办法:

1:打破双亲委托~不让父加载器去加载就行了~~~

2:把我们工作空间的类剪切到桌面去,父加载器加载不到就只能自定义类加载器去加载了!!!

这里我选择第二种来演示一下,剪切后的运行结果:

find class successfully
com.lxl.jvm.MyTest01@330bedb4
Mytest16 [classLoaderName=load1]
find class successfully
com.lxl.jvm.MyTest01@7ea987ac
Mytest16 [classLoaderName=load2]
 

舒服~~这回就是我们的自定义类加载器加载的了

另外文章前面说了,idea会将我们工作空间地址配置给应用类加载器。具体每个类加载器加载的地址是什么呢

我们一起在看一下

public class MyTest18 {
	public static void main(String[] args) {
		System.out.println(System.getProperty("sun.boot.class.path"));
		System.out.println(System.getProperty("java.ext.dirs"));
		System.out.println(System.getProperty("java.class.path"));
	}
}

我这里的输入是

D:\Program Files\Java\jdk1.8.0_111\jre\lib\resources.jar;D:\Program Files\Java\jdk1.8.0_111\jre\lib\rt.jar;D:\Program Files\Java\jdk1.8.0_111\jre\lib\sunrsasign.jar;D:\Program Files\Java\jdk1.8.0_111\jre\lib\jsse.jar;D:\Program Files\Java\jdk1.8.0_111\jre\lib\jce.jar;D:\Program Files\Java\jdk1.8.0_111\jre\lib\charsets.jar;D:\Program Files\Java\jdk1.8.0_111\jre\lib\jfr.jar;D:\Program Files\Java\jdk1.8.0_111\jre\classes
D:\Program Files\Java\jdk1.8.0_111\jre\lib\ext;C:\Windows\Sun\Java\lib\ext
E:\workspace\jvm_learning\bin\main;E:\maven\repository\caches\modules-2\files-2.1\commons-collections\commons-collections\3.2\f951934aa5ae5a88d7e6dfaa6d32307d834a88be\commons-collections-3.2.jar;E:\maven\repository\caches\modules-2\files-2.1\cglib\cglib\3.2.8\331c90f8cd3be5b2ee225354bed68862ba0fd3c8\cglib-3.2.8.jar;E:\maven\repository\caches\modules-2\files-2.1\com.google.guava\guava\29.0-jre\801142b4c3d0f0770dd29abea50906cacfddd447\guava-29.0-jre.jar;E:\maven\repository\caches\modules-2\files-2.1\org.ow2.asm\asm\6.2.1\c01b6798f81b0fc2c5faa70cbe468c275d4b50c7\asm-6.2.1.jar;E:\maven\repository\caches\modules-2\files-2.1\org.apache.ant\ant\1.10.3\88becdeb77cdd2457757b7268e1a10666c03d382\ant-1.10.3.jar;E:\maven\repository\caches\modules-2\files-2.1\com.google.guava\failureaccess\1.0.1\1dcf1de382a0bf95a3d8b0849546c88bac1292c9\failureaccess-1.0.1.jar;E:\maven\repository\caches\modules-2\files-2.1\com.google.guava\listenablefuture\9999.0-empty-to-avoid-conflict-with-guava\b421526c5f297295adef1c886e5246c39d4ac629\listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar;E:\maven\repository\caches\modules-2\files-2.1\com.google.code.findbugs\jsr305\3.0.2\25ea2e8b0c338a877313bd4672d3fe056ea78f0d\jsr305-3.0.2.jar;E:\maven\repository\caches\modules-2\files-2.1\org.checkerframework\checker-qual\2.11.1\8c43bf8f99b841d23aadda6044329dad9b63c185\checker-qual-2.11.1.jar;E:\maven\repository\caches\modules-2\files-2.1\com.google.errorprone\error_prone_annotations\2.3.4\dac170e4594de319655ffb62f41cbd6dbb5e601e\error_prone_annotations-2.3.4.jar;E:\maven\repository\caches\modules-2\files-2.1\com.google.j2objc\j2objc-annotations\1.3\ba035118bc8bac37d7eff77700720999acd9986d\j2objc-annotations-1.3.jar;E:\maven\repository\caches\modules-2\files-2.1\org.apache.ant\ant-launcher\1.10.3\9dd5189e7f561ca19833b4e3672720b9bc5cb2fe\ant-launcher-1.10.3.jar

还是非常的长的,但是很明显的就有我的工作空间。可以看下自己的

sun.boot.class.path

java.ext.dirs

java.class.path

分别对应根加载器,扩展类加载器和应用类加载器的地址

 

JVM的类加载器大概就是这些东西,参考了一些视频与文章,具体哪个就不说了。其实自己已经学完挺久了,感觉以后怕忘了,就随手写了一些。自己看完也很容易再回忆起来、

分了10小篇文章,前几篇都是一个实例加几行注释,这篇稍微多一点。以前感觉jvm离自己挺远的,深入学习之后感觉以前不理解的东西一下子都清晰了很多,可能在工作中写业务代码并没啥用~~但是多了解一些没啥坏处

类加载就结束了,下面会分享jvm字节码相关的一些内容。我也忘得差不多了,就当复习了!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值