双亲委派
双亲委派:如果一个类加载器收到了加载某个类的请求,则该类加载器并不会去加载该类,而是把这个请求委派给”父类加载器“,每一个层次的类加载器都是如此,因此所有的类加载请求最终都会传送到顶端的启动类加载器;只有当”父类加载器“在其搜索范围内无法找到所需的类,并将该结果反馈给子类加载器,子类加载器会尝试去自己加载。
jvm中加载器层次关系:
启动类加载器BootStrapClassLoader --------> 扩展类加载器ExtensionClassLoader -------->
应用加载器AppClassLoader --------> 自定义加载器
-
BootStrapClassLoader:
加载/lib/下的jar(包括Java核心类库rt.jar等其他),因为是C++写的,程序里面打印出来是null
官方注释:Some implementations may use null to represent the bootstrap class loader
-
ExtensionClassLoader:
加载/lib/ext/下的jar,Java实现,可以在程序里里面获取
-
AppClassLoader:
又叫SystemClassLoader,classpath下面的即我们写的代码,以及第三方引入的依赖,都是他加载的
示例代码:
package day20200228;
import com.sun.nio.zipfs.ZipFileSystem;
import junit.extensions.TestDecorator;
import sun.net.spi.nameservice.dns.DNSNameService;
import javax.crypto.Cipher;
/**
* @Author: xiaoshijiu
* @Date: 2020/2/29
* @Description: $value$
*/
public class TestClassLoad {
public static void main(String[] args) {
/**
* BootStrapClassLoad
*/
System.out.println(Object.class.getClassLoader()); // rt.jar
System.out.println(String.class.getClassLoader()); // rt.jar
System.out.println(Math.class.getClassLoader()); // rt.jar
System.out.println(Cipher.class.getClassLoader()); // jce.jar
System.out.println("================");
/**
* ExtensionClassLoad
*/
System.out.println(ZipFileSystem.class.getClassLoader()); // ext/zipfs.jar
System.out.println(DNSNameService.class.getClassLoader()); // ext/dnsns.jar
System.out.println("================");
/**
* AppClassLoad/SystemClassLoad
*/
System.out.println(TestClassLoad.class.getClassLoader()); // 自己写的
System.out.println(ClassLoadingProcessStatic.class.getClassLoader()); // 自己写的
System.out.println(TestDecorator.class.getClassLoader()); // 引入的junit依赖中的
}
}
运行结果
双亲委派的源码实现
在java.lang.ClassLoader
中的,通过loadClass
方法加载类,也就是双亲委派的具体实现
// name参数,即binary name,类的全类名
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 首先,检查这个类是不是被加载过了;findLoadedClass方法检查
Class<?> c = findLoadedClass(name);
// 没有被加载过
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
// ”父类“不为空,找父类加载器加载;loadClass方法:仍然调用该方法,即产生递归;
c = parent.loadClass(name, false);
} else {
// ”父类“为空,即代表”父类“为BootstrapClassLoader,找其加载
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
//异常
}
if (c == null) {
long t1 = System.nanoTime();
// 如果还没有加载到,c仍然为空,那么调用该加载器的findClass方法,去加载
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;
}
}
上面的一步一步走下来,注释写得都很清楚,流程也是很清楚了;
再看看基类java.lang.ClassLoader
中的findClass
方法
空方法,没有实现,所以要想自定义类加载器,必须要继承基类并重写该findClass
方法
`findClass`方法将 binary name 变成 Class 对象
这里有一个关于binary name的解释:
$ 表示里面的内部类,$ 数字里面的第几个匿名内部类
自定义类加载器
前面说了要想自定义类加载器,必须要继承基类并重写该findClass
方法
findClass方法 将 binary name 变成 Class 对象
另外在结合: defineClass方法 可以将class文件内容的字节数组转成Class对象
defineClass方法 在基类`java.lang.ClassLoader`中有实现
这样一来,我们只需要 根据class文件名 binary name 获得 class文件内容的byte[]形式
再根据defineClass方法,将byte[]数据变为Class对象,完成。
package day20200229;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
/**
* @Author: xiaoshijiu
* @Date: 2020/2/29
* @Description: 自定义 ClassLoader
* 继承 基类ClassLoader,重写 findClass方法
* findClass方法:将 binary name 变成 Class对象
* 其中还需要另外一个方法 defineClass :可以将class文件的字节数组转成Class对象
*/
public class MyClassLoader extends ClassLoader {
static String path;
public MyClassLoader() {
super();
}
// 指定 上级类加载器的构造方法
public MyClassLoader(ClassLoader parent) {
super(parent);
}
/**
* @param name binary name
*/
@Override
protected Class<?> findClass(String name) {
byte[] bytes = classToBytes(name);
return defineClass(name, bytes, 0, bytes.length);
}
/**
* 抽取出来一个私有方法,将class文件内容变成bytes数组
*
* @param name binary name
*/
private byte[] classToBytes(String name) {
FileInputStream inputStream = null;
ByteArrayOutputStream outputStream = null;
try {
// 判空
if (name == null || "".equals(name)) {
return null;
}
// 加载指定path路径的class文件
if (path == null) {
// 文件位置,.替换成/
name = name.replace(".", "/") + ".class";
} else {
name = path + name.replace(".", "/") + ".class";
}
inputStream = new FileInputStream(name);
outputStream = new ByteArrayOutputStream();
// 一次接受50字节
byte[] bytes = new byte[50];
int len;
while ((len = inputStream.read(bytes)) != -1) {
outputStream.write(bytes, 0, len);
}
return outputStream.toByteArray();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
if (outputStream != null) {
outputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
// 测试代码
public static void main(String[] args) throws ClassNotFoundException {
MyClassLoader classLoader = new MyClassLoader();
// classpath下的类(自己写的类),双亲委派机制,由AppClassLoader加载
Class<?> c = classLoader.loadClass("day20200222.TestVolatile");
System.out.println(c.getClassLoader());
// 硬盘中的类(g:/day20190820.ListNodeSum.class),双亲都加载不了,由该自定义的加载器加载
path = "g:/";
Class<?> c2 = classLoader.loadClass("day20190820.ListNodeSum");
System.out.println(c2.getClassLoader());
}
}
上面两组测试代码:
- 第一,
day20200222.TestVolatile该类位于当前项目工程中,即classpath下,根据双亲委派机制,由AppClassLoader加载
- 第二,
day20190820.ListNodeSum位于g盘中,根据双亲委派机制,”父类加载器“均不能加载,最后反馈到该类加载器,其findClass方法可以正确加载,即自定义加载器完成加载
运行结果