后面就是使用和卸载的过程。
类加载器就是将class文件加载到jvm中。
验证三种加载器加载的类文件:
public static void main(String[] args) { System.out.println("bootstrapLoader加载以下文件:"); URL[] urls = Launcher.getBootstrapClassPath().getURLs(); for (int i = 0; i < urls.length; i++) { System.out.println(urls[i]); } System.out.println(); System.out.println("extClassloader加载以下文件:"); System.out.println(System.getProperty("java.ext.dirs")); System.out.println(); System.out.println("appClassLoader加载以下文件:"); System.out.println(System.getProperty("java.class.path")); }
在这里插入图片描述
从图可以看出类加载对只会加载自己负责的那部分class文件到内存中。
我们通过看启动器(Launch)构造方法里面的内容,来一探究竟 类加载器是如何初始化的
public Launcher() { Launcher.ExtClassLoader var1; try { var1 = Launcher.ExtClassLoader.getExtClassLoader(); } catch (IOException var10) { throw new InternalError("Could not create extension class loader", var10); } try { this.loader = Launcher.AppClassLoader.getAppClassLoader(var1); } catch (IOException var9) { throw new InternalError("Could not create application class loader", var9); }}
在构造方法中,可以看出系统创建了两个加载器,分别为:ExtClassLoader和AppClassLoader,我们平时默认使用的类加载器就是使用
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
这个加载器加载的,我们平时调用Class.class.getClassLoader()
方法返回的就是这个初始化的加载器
我们执行如下代码
public static void main(String[] args) { System.out.println(TestJVMClassLoader.class.getClassLoader().toString()); System.out.println(TestJVMClassLoader.class.getClassLoader().getParent().toString()); System.out.println(TestJVMClassLoader.class.getClassLoader().getParent().getParent()); }
可以发现加载我们创建代码的类加载器是AppClassLoader,AppClassLoader的父类是ExtClassLoader,ExtClassLoader的父类是null,这里出现了两个类加载器,还有一个引导类加载器呢,如果没有猜错的话,应该就是null,那么为什么会是null呢? 还记得我们前面说过引导类加载器是是用C语言写的,既然是C语言写的,又怎么能打印在这呢,所以我们现在画个图来梳理下这三者的关系:
有很多同学都听过这个名词,但是就是没有一篇文章能讲清楚到底什么是双亲委派机,今天我用我毕生所学,让同学们彻底理解什么是双亲委派机制。
我们直接看源码,就很容易理解什么是双亲委派;
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; } }
我来解释下这段代码的执行过程:
protected Class<?> findClass(String name) throws ClassNotFoundException { throw new ClassNotFoundException(name); }
我们用一张图来说下找“class的过程”
其实最后一步,自己想办法,也就是实现父类的findclass方法。想必大家在看完这段讲解后,对双亲委派应该有个大致的了解了,如果真的认真看完这个流程的话,相信大家肯定会有疑问: 如果自己需要加载这个字节码的话,为什么不直接调用自己的findclass方法呢,还得一级一级往上找呢,JVM为什么要设置双亲委派机制呢?
package java.util; public class Date { public static void main(String[] args) { System.out.println("我被执行"); } }
执行结果:
为什么会出现这种情况呢,main方法为什么找不到呢?其实这就是双亲委派机制在起作用,因为java系统中已经有同包名的Date类了,当我们运行我们的main方法是,他首先得要加载Date类。根据双亲委派机制,AppClassLoader得先询问父加载器有没有加载过这个Date,经过询问发现,父类已经加载了这个类,所以AppClass就不要自己再加载一遍了,直接使用父加载器加载的系统Date类,但是系统Date类是没有main方法的。所以才会出现上面的错误。
package com.lezai; import java.io.FileInputStream; import java.lang.reflect.Method; public class CustomClassloaderTest { static class CustomClassloader extends ClassLoader{ private String classPath = "/Users/yangle/Desktop"; /** * 自己实现查找字节码文件的逻辑,可以来自本地磁盘,也可以是来自网络 * @param name * @return * @throws ClassNotFoundException */ @Override protected Class<?> findClass(String name) throws ClassNotFoundException { try { byte[] data = loadByte(name); return defineClass(name, data, 0, data.length); } catch (Exception e) { e.printStackTrace(); } return null; } // 将文件读取到字节数组 private byte[] loadByte(String name) throws Exception { name = name.replaceAll("\\.", "/"); FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class"); int len = fis.available(); byte[] data = new byte[len]; fis.read(data); fis.close(); return data; } } public static void main(String[] args) { CustomClassloader classLoader = new CustomClassloader(); try { Class clazz = classLoader.loadClass("com.lezai.Test"); Object obj = clazz.newInstance(); Method method= clazz.getDeclaredMethod("out", String.class); method.invoke(obj,"乐哉"); System.out.println(clazz.getClassLoader()); } catch (Exception e) { e.printStackTrace(); } } }
实现步骤:
我们如果需要打破双亲委派机制,只需要自己实现loadClass方法,不再去询问父类中是否加载过我们需要的字节码文件,然后直接调用findClass加载我们的类就行了。
以Tomcat类加载为例,Tomcat 如果使用默认的双亲委派类加载机制行不行?
作者:乐哉
图片:来源于网络,如有侵权,联系删除。
本文分享自微信公众号 - 乐哉开讲(Lezaikaijiang),作者:乐哉开讲
原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。
原始发表时间:2020-10-22
本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。
我来说两句