Java Classloader是Java Runtime Environment的一部分,负责动态加载Java字节码文件到Java虚拟机的内存空间中。
类加载过程
加载
- 通过类的全限定名来获取该类的二进制字节流
- 把二进制字节流转化为方法区的运行时数据结构
- 在堆上创建一个java.lang.Class对象,用来封装类在方法区数据结构,并向外提供访问方法区内部数据结构的接口
加载方式
- 从本地文件系统、jar等归档文件中加载
- 将Java源文件动态编译为class
- 网络下载、从专有数据库中加载等
验证
校验字节码文件的正确性
准备
给类的静态变量分配内存,并赋予默认值
解析
将常量池中的符号引用转换为直接引用
初始化
对类的静态变量初始化为指定的值,执行静态代码块
使用
卸载
类加载器类型
启动类加载器
BootstrapClassLoader, 用于加载启动基础模块类,负载加载支撑JVM运行的位于JRE的lib目录下的核心类库,比如rt.jar、charsets.jar等
扩展类加载器
ExtClassLoader, 负责加载支撑JVM运行的位于JRE的lib目录下的ext扩展目录中的JAR类包
平台类加载器
PlatformClassLoader, JDK8之后替换了扩展类加载器
应用程序类加载器
AppClassLoader, 负责加载ClassPath路径下的类包,主要就是加载你自己写的那些类
自定义类加载器
负责加载用户自定义路径下的类包,java.lang.ClassLoader的子类
类加载器的关系
启动类加载器->平台类加载器或扩展类加载器->应用程序类加载器->自定义类加载器,前者是后者的父级
双亲委派机制
双亲委派机制说简单点就是,先找父亲加载,不行再由儿子自己加载
为什么需要双亲委派机制?
- 沙箱安全机制:自己写的java.lang.String.class类不会被加载,这样便可以防止核心API库被随意篡改
- 避免类的重复加载:当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次,保证被加载类的唯一性
class ClassLoaderTest {
public static void main(String[] args) {
//null,启动类加载器是C++语言实现,所以打印不出来
System.out.println(String.class.getClassLoader());
//sun.misc.Launcher$ExtClassLoader@1b28cdfa
System.out.println(com.sun.crypto.provider.DESKeyFactory.class.getClassLoader());
//sun.misc.Launcher$AppClassLoader@18b4aac2
System.out.println(ClassLoaderTest.class.getClassLoader());
System.out.println("");
//sun.misc.Launcher$ExtClassLoader
System.out.println(MyClassLoader.class.getClassLoader().getParent());
//null
System.out.println(MyClassLoader.class.getClassLoader().getParent().getParent());
}
}
/**
* 自定义ClassLoader
*/
class MyClassLoader extends ClassLoader{
private String name;
public MyClassLoader(String name) {
this.name = name;
}
/*
* 重写findClass方法
*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] data = loadData(name);
return this.defineClass(name, data, 0, data.length);
}
/*
* 自定义类加载器去classes下找对应的字节码文件
*/
private byte[] loadData(String name) {
name = name.replace(".", "/");
String clazzFilePath = "classes/" + name + ".class";
byte[] data = null;
InputStream in = null;
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
in = new FileInputStream(new File(clazzFilePath));
int a = 0;
while ((a = in.read()) != -1){
out.write(a);
}
data = out.toByteArray();
} catch (Exception e) {
e.printStackTrace();
}
return data;
}
}
class Person {
}
/*
* 1.直接测试, 输出sun.misc.Launcher$AppClassLoader@18b4aac2
* 2.移动out/production/java/com/example/jvm/loader/Person.class到classes/com/example/jvm/loader/Person.class
* 3.再次运行输出com.example.jvm.loader.MyClassLoader@74a14482,使用了我们自定义的类加载器
*/
class MyClassLoaderTest {
public static void main(String[] args) throws ClassNotFoundException {
MyClassLoader myClassLoader = new MyClassLoader("MyClassLoader");
Class aClass = myClassLoader.loadClass("com.example.jvm.loader.Person");
System.out.println(aClass.getClassLoader());
}
}