参考视频:
系统学习让你轻松定义java类加载器_哔哩哔哩_bilibili
本文代码:
GitHub - CodePpoi/classloader-learn
参考博客:
视频学习笔记 | 系统学习让你轻松定义 Java 类加载器 · 语雀
随便写写,通过loadClass方法来加载,先看该ClassLoader是否有父类,有则递归调用父类的loadClass,父类能加载则直接返回父类加载的结果,如果父类无法加载,则调用自身的findClass方法:
不同加载器加载的同一个类,两者不相等
package com.company;
import java.io.IOException;
import java.io.InputStream;
public class ClassLoaderTest {
public static void main(String[] args) throws Exception {
ClassLoader myLoader = new ClassLoader() {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
try {
String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
InputStream is = getClass().getResourceAsStream(fileName);
if (is == null) {
return super.loadClass(name);
}
byte[] b = new byte[is.available()];
is.read(b);
return defineClass(name, b, 0, b.length);
} catch (IOException E) {
throw new ClassNotFoundException();
}
}
};
Object obj = myLoader.loadClass("com.company.ClassLoaderTest").newInstance();
System.out.println(obj.getClass());
System.out.println(obj instanceof com.company.ClassLoaderTest);
}
}
loadClass()、findClass()、defineClass()区别见https://www.jianshu.com/p/b75a89014e27
从名字就可以看出来,load是加载,find是找到class文件(我的理解就是从文件路径读取文件,然后转换成字节流或者说字节数组,然后字节流作为入参传入defineClass方法),然后define是把class文件转换成类(Class),自定义类加载器一般覆盖findClass方法
字节码文件就是 .class,这个文件是加载到JVM虚拟机的方法区,加载到方法区的内容,可参考IDEA使用jclasslib-bytecode-viewer插件查看字节码_newbaby2012的专栏-CSDN博客
我们也是根据方法区的字节码,来创建对应类的Class对象的,就是比如 sample.Class 对象
类加载器三个阶段
1. 加载
把.class文件加载到方法区
2. 连接
分为3小部分
a 验证 验证当前文件对JVM虚拟机是安全的,不会导致其崩溃
b 准备 对静态static变量分配内存,并赋默认值(非初始值,int 默认值为0,但你可能这么定义了static int a = 5,在这个阶段只会把a设置成0。赋值为5的操作是初始化时做)
c 解析
3. 初始化
加载器
Java自带加载器:
根类加载器BootStrap,没有继承任何加载器,使用c++编写
扩展类加载器Extension,继承自BootStrap,使用java编写,
系统类加载器AppClassLoader,也叫应用加载器,继承自Extension,用户自定义的类加载器的默认父加载器,就是比如你在你的Project里面加了一个Demo.Java文件,那么Demo.class.getClassLoader()得到的就是AppClassLoader
package com.company;
import sun.net.spi.nameservice.dns.DNSNameService;
import java.io.File;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
public class Main {
public static void main(String[] args) throws Exception {
// BootStrap加载器,C++编写,加载<JAVA_HOME>\jre\lib里面的类库(比如rt.jar,rt是runtime的简称)
ClassLoader classLoader = Object.class.getClassLoader();
System.out.println(classLoader); // 打印null
classLoader = Number.class.getClassLoader();
System.out.println(classLoader); // 打印null
//Extension加载器,纯java编写,ExtClassLoader,加载<JAVA_HOME>\jre\lib\ext里面的类库(比如dnsns.jar)或者系统变量"java.ext.dirs"指定目录下面的类
ClassLoader classLoader1 = DNSNameService.class.getClassLoader();
System.out.println(classLoader1);
//System加载器,纯java编写,AppclassLoader,我们写的java程序一般由这个加载
ClassLoader classLoader2 = Main.class.getClassLoader();
System.out.println(classLoader2);
MyFileClassLoader classLoader3 = new MyFileClassLoader("");
Class<?> clazz = classLoader3.loadClass("com.company.Demo");
clazz.newInstance();
//自己定义的类,默认继承AppClassLoader
System.out.println(clazz.getClassLoader());
// File file = new File("d:/");
// URI uri = file.toURI();
// URL url = uri.toURL();
//
// URLClassLoader classLoader3 = new URLClassLoader()
}
}
Java虚拟机按需加载,只有当用到该类时,才会将它的class文件(class文件在编译时生成)加载到虚拟机内存,并生成class对象
package com.company;
// 1. 继承ClassLoader
// 2. 覆盖 findClass方法
public class MyFileClassLoader extends ClassLoader {
private String directory; // 被加载类所在的目录
public MyFileClassLoader(String directory) { // 默认父类加载器就是系统类加载器 AppClassLoader
this.directory = directory;
}
public MyFileClassLoader(ClassLoader parent, String directory) {
super(parent);
this.directory = directory;
}
}
URLClassLoader
扩展了ClassLoader,从指定URL地址(这个地址可以指向磁盘,也可以指向网络地址),用我们指定的classLoader加载
显示加载和隐式加载
显示 调用class.ForName和loadClass来加载
隐式 不需要调用上面的方法,而是java虚拟自动装载到内存,比如Demo.java文件中有代码 new Demo2(),那么在加载Demo.class的时候会自动加载Demo2.class到虚拟机内存中