自定义类加载器自定义类加载器只需要继承 java.lang.ClassLoader 类,该类有两个核心方法,一个是loadClass(String, boolean),实现了双亲委派机制,还有一个方法是findClass,默认实现是空方法,所以我们自定义类加载器主要是重写findClass方法 。
public class MyClassLoaderTest {static class MyClassLoader extends ClassLoader {private String classPath;public MyClassLoader(String classPath) {this.classPath = classPath;}private byte[] loadByte(String name) throws Exception {name = name.replaceAll("\\.", "/");FileInputStream fis = new FileInputStream(classPath + "/" + name+ ".class");int len = fis.available();byte[] data = https://www.huyubaike.com/biancheng/new byte[len];fis.read(data);fis.close();return data;}protected Class> findClass(String name) throws ClassNotFoundException {try {byte[] data = loadByte(name);//defineClass将一个字节数组转为Class对象,这个字节数组是class文件读取后最终的字节数组 。return defineClass(name, data, 0, data.length);} catch (Exception e) {e.printStackTrace();throw new ClassNotFoundException();}}}public static void main(String[] args) throws Exception {//初始化自定义类加载器,会先初始化父类ClassLoader,其中会把自定义类加载器的父加载器设置为应用程序类加载器AppClassLoaderMyClassLoader classLoader = new MyClassLoader("D:/test");//D盘创建 test/com/hyz/jvm 几级目录,将User类的复制类User1.class丢入该目录Class clazz = classLoader.loadClass("com.hyz.jvm.User1");//Class clazz = classLoader.loadClass("java.lang.String");Object obj = clazz.newInstance();Method method = clazz.getDeclaredMethod("hello", null);method.invoke(obj, null);System.out.println(clazz.getClassLoader().getClass().getName());}}
打破双亲委派机制?假如我们的target下有一个User类,但是我们的程序代码中需要去读取D:/test/com/hyz/jvm/User.class类,根据双亲委派机制,肯定是会加载到target下的User类的,要如何才能加载到D:/test下的User类呢?
?那意味着我们需要去打破双亲委派机制 。看AppClassLoader的类加载逻辑,主要逻辑在父类ClassLoader.loadClass()方法中,我们只需要在自定义的类加载器中重写该方法即可 。主要修改逻辑:如果类型是com.hyz.jvm开头的类,则从自定义类加载器中去读取,否则委托给上层类加载器加载 。
/** * 32 * 重写类加载方法,实现自己的加载逻辑,不委派给双亲加载 * 33 * @param name * 34 * @param resolve * 35 * @return * 36 * @throws ClassNotFoundException * 37 */protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException {synchronized (getClassLoadingLock(name)) {// First, check if the class has already been loadedClass<?> c = findLoadedClass(name);if (c == null) {// If still not found, then invoke findClass in order// to find the class.long t1 = System.nanoTime();if (!name.startsWith("com.hyz.jvm")) {c = this.getParent().loadClass(name);} else {c = findClass(name);}// this is the defining class loader; record the statssun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}if (resolve) {resolveClass(c);}return c;}}
?应用到打破双亲委派机制的实际应用场景是在Tomcat加载war包 。比如war1用的是spring4版本,war2用的是spring5版本,那就意味着加载着2个war包不能用同一个类加载器实例,需要各自指定一个自定义的类加载器实例,各自去加载所需的spring版本库文件 。
总结:双亲委派机制保证了核心类的安全,确保不会被修改,也保证了不会加载到重复的字节码文件 。
经验总结扩展阅读
- 万字详解JVM,让你一文吃透
- JVM学习笔记——类加载和字节码技术篇
- 详细了解JVM运行时内存
- 【JVM】关于JVM,你需要掌握这些 | 一文彻底吃透JVM系列
- JVM学习笔记——垃圾回收篇
- JVM学习笔记——内存结构篇
- JAVA系列之JVM内存调优
- JDK中自带的JVM分析工具
- 树的邻接矩阵、双亲孩子表示法…… C++ 不知树系列之初识树
- JVM、JDK、JRE你分的清吗