手把手教你全面掌握-类加载机制(二)_双亲委派简介(1),应届毕业生销售岗位面试常见问题

12 阅读6分钟

由上面的代码我们引发一个思考,系统类加载器的父类加载器是扩展类加载器,扩展类加载器的父类加载器是启动类加载器,是否真的是这样呢?我们通过代码来测试一下:

/** *

* 测试类加载器之间的关系 *

* * @Author: Liziba * @Date: 2021/5/31 21:14 */ public class ClassLoaderRelationshipTest {

public static void main(String[] args) {

System.out.println(ClassLoader.getSystemClassLoader()); System.out.println(ClassLoader.getSystemClassLoader().getParent()); System.out.println(ClassLoader.getSystemClassLoader().getParent().getParent());

} }

输出结果:
在这里插入图片描述

通过上述的测试代码和输出结果,可以非常明确的看出ClassLoader.getSystemClassLoader()可以直接获取系统类加载器,而通过ClassLoader.getSystemClassLoader().getParent()可以看出系统类加载器的父类加载器是扩展类加载器,但是ClassLoader.getSystemClassLoader().getParent().getParent()的输出结果为null,是否说明我们的猜想存在问题呢?事实上,由于启动类加载器无法直接通过Java代码获取,他是在虚拟机中实现的,JVM默认采用null来代表启动类加载器。这个点我们可以通过ClassLoader的构造函数中知晓。

// parent设置为私有属性,并且未提供设置接口(Setter方法) private final ClassLoader parent;

// getSystemClassLoader()方法为获取系统类加载器 protected ClassLoader() { this(checkCreateClassLoader(), getSystemClassLoader()); }

// 强制设置父类加载器 protected ClassLoader(ClassLoader parent) { this(checkCreateClassLoader(), parent); }

// 在不指定明确的父类加载器时,设置parent加载器为系统类加载器 private ClassLoader(Void unused, ClassLoader parent) { this.parent = parent; if (ParallelLoaders.isRegistered(this.getClass())) { parallelLockMap = new ConcurrentHashMap<>(); package2certs = new ConcurrentHashMap<>(); domains = Collections.synchronizedSet(new HashSet()); assertionLock = new Object(); } else { // no finer-grained lock; lock on the classloader instance parallelLockMap = null; package2certs = new Hashtable<>(); domains = new HashSet<>(); assertionLock = this; } }

二、双亲委派机制示例

1、创建测试bean

package com.liziba.classloader.bean;

public class Person {

private String name;

}

2、当前工程中创建测试类

package com.liziba.classloader;

public class ClassLoaderRuleTest {

public static void main(String[] args) { try { // 查看Java 类路径 System.out.println(System.getProperty("java.class.path")); // 调用加载当前类的类加载器加载测试类ClassLoaderRuleTest Class<?> clazz = Class.forName("com.liziba.classloader.ClassLoaderRuleTest"); // 查看加载当前测试类的类加载器 System.out.println(clazz.getClassLoader()); } catch (ClassNotFoundException e) { e.printStackTrace(); } } }

输出结果:
在这里插入图片描述

3、将Person.class打包成test.jar复制到<JAVA_RUNTIME_HOME>\lib\ext目录下

在这里插入图片描述

再次运行代码测试,查看输出结果:sun.misc.Launcher$ExtClassLoader@7f31245a

由上可以证明前面说的双亲委派机制:系统类加载器在接收到加载请求时,首先将请求委派给父类加载器(标准扩展类加载器)进行加载,而在上面的示例中扩展类加载器抢先加载类Person.class的加载请求。

在这里插入图片描述

4、将test.jar复制到<JAVA_RUNTIME_HOME>\lib目录下

在这里插入图片描述

输出结果:
在这里插入图片描述

第四步和第三步输出的结果是一致的,Person.class的加载请求都有扩展类加载器加载,这和前面所说的双亲委派机制并不矛盾。JVM出于安全考虑,不会加载<JAVA_HOME>/lib目录下存在的陌生类,只能加载JVM指定的类。

5、删除<JAVA_RUNTIME_HOME>\lib\ext的test.jar和当前目录下编译的Person.class

输出结果:系统抛出java.lang.ClassNotFoundException

在这里插入图片描述

三、开发自己的类加载器

在类加载过程中,真正完成类的加载工作的类加载器和启动这个加载过程的类加载器,有可能不是同一个。真正完成类的加载工作是通过调用defineClass来实现的;而启动类的加载过程是通过调用loadClass来实现的。前者称为一个类的定义加载器(defining loader),后者称为初始加载器(initiating loader)。在Java虚拟机判断两个类是否相同的时候,使用的是类的定义加载器。也就是说,哪个类加载器启动类的加载过程并不重要,重要的是最终定义这个类的加载器。方法 loadClass()抛出的是 java.lang.ClassNotFoundException异常;方法 defineClass()抛出的是 java.lang.NoClassDefFoundError异常。类加载器在成功加载某个类之后,会把得到的 java.lang.Class类的实例缓存起来。下次再请求加载该类的时候,类加载器会直接使用缓存的类的实例,而不会尝试再次加载。也就是说,对于一个类加载器实例来说,相同全名的类只加载一次,即 loadClass方法不会被重复调用。

1、文件系统类加载器

package com.liziba.classloader;

import java.io.*;

/** *

* 文件系统类加载器 *

* * @Author: Liziba * @Date: 2021/5/31 23:04 */ public class FileSystemClassLoader extends ClassLoader {

/** 指定文件路径 */ private String rootDir;

public FileSystemClassLoader(String rootDir) { this.rootDir = rootDir; }

@Override protected Class<?> findClass(String name) throws ClassNotFoundException { byte[] data = getClassByteData(name); if (data == null || data.length == 0) { throw new ClassNotFoundException(); } else { return defineClass(name, data, 0, data.length); } }

/** * 读取类的字节流、获取字节数组 * * @param className * @return */ private byte[] getClassByteData(String className) { String path = classNameCovertToPath(className); try { InputStream in = new FileInputStream(path); ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] buffer = new byte[1024 * 4]; int len = 0; while ((len = in.read(buffer)) != -1) { baos.write(buffer, 0, len); } return baos.toByteArray(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return null; }

/** * 类权限定名转绝对路径 * * @param className * @return */ private String classNameCovertToPath(String className) { return rootDir + File.separatorChar + className.replace('.', File.separatorChar) + ".class"; }

}

测试类

package com.liziba.classloader;

import com.liziba.classloader.bean.Person;

/** *

* 测试文件类加载器 *

* * @Author: Liziba * @Date: 2021/5/31 23:12 */ public class TestFileSystemClassLoader {

public static void main(String[] args) {

String rootDir = "E:\workspaceall\liziba-java\out\production\liziba-java"; String className = "com.liziba.classloader.bean.Person"; FileSystemClassLoader fscl = new FileSystemClassLoader(rootDir); Class<?> clazz = null; try { clazz = fscl.findClass(className); Object object = clazz.newInstance(); System.out.println(object); System.out.println(object.getClass().getClassLoader()); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } } }

输出结果:
在这里插入图片描述

2、网络类加载器

package com.liziba.classloader;

import sun.nio.ch.Net;

import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.net.URL;

/** *

* 网络类加载器 *

* * @Author: Liziba * @Date: 2021/5/31 23:25 */ public class NetworkClassLoader extends ClassLoader{

/** 指定网络URL */ private String rootUrl;

public NetworkClassLoader(String rootUrl) { this.rootUrl = rootUrl; }

@Override protected Class<?> findClass(String name) throws ClassNotFoundException { byte[] classData = getClassData(name); if (classData == null) { throw new ClassNotFoundException(); } else { return defineClass(name, classData, 0, classData.length); } }

/** * 从网络上获取类的字节数组 * * @param className * @return */ private byte[] getClassData(String className) {

String path = classNameCovertToPath(className); try {

img img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!