文章目录
一、类加载器介绍
1)引导类加载器(BootstrapClassLoader)
也叫启动类或根类加载器,它用来加载 Java 的核心类,是用原生代码来实现的,并不继承自 java.lang.ClassLoader(负责加载$JAVA_HOME中jre/lib/rt.jar里所有的class,由C++实现,不是ClassLoader子类)。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作。
2)扩展类加载器(ExtensionsClassLoader)
它负责加载JRE的扩展目录,lib/ext或者由java.ext.dirs系统属性指定的目录中的JAR包的类。由Java语言实现,父类加载器为null。
3)系统类加载器(SystemClassLoader)
被称为系统(也称为应用)类加载器,它负责在JVM启动时加载来自Java命令的-classpath选项、java.class.path系统属性,或者CLASSPATH换将变量所指定的JAR包和类路径。程序可以通过ClassLoader的静态方法getSystemClassLoader()来获取系统类加载器。如果没有特别指定,则用户自定义的类加载器都以此类加载器作为父加载器。由Java语言实现,父类加载器为ExtClassLoader。
二、机制说明
1、加载的机制
通过上面的图我们来理解一下这个加载的机制,图中绿色代表加载请求,红色代码返回结果;如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式。
2、机制的优势
双亲机制给JVM的加载带来了层级关系,这种层级关系避免了类的重复加载,同时java有许多核心库,这就避免了外围代码对核心API的渗透和篡改,保障了java本身架构的安全。
3、案例说明
这里自定义加载类,重新改写java.lang.String
,然后看它们的加载器是否一样
自定义加载器的方法可以查看这篇博客,这里就不去重复定义了。https://blog.csdn.net/X_ABU/article/details/107412051
自定义加载器
package com.main.jvmtexst.classloadertest.test2;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
/**
* 自定义加载器
*/
public class CustomizeClassLoader extends ClassLoader {
//目录地址
private String path;
//类全限定名称地址
private String classPath;
//默认使用系统加载器加载
public CustomizeClassLoader(String path) {
this.path = path;
}
//选择自己的父类加载器,如果使用引导类加载器则使用null代替
public CustomizeClassLoader(ClassLoader parent, String path) {
super(parent);
this.path = path;
}
private String getClassPath() {
//处理全限定名,转换成地址形式
List<String> nameList = Arrays.asList(classPath.split("\\."));
StringBuilder nameBuilder = new StringBuilder();
nameList.forEach(item -> nameBuilder.append(item).append(File.separator));
classPath = nameBuilder.toString();
classPath = classPath.substring(0, classPath.length() - 1);
//返回全地址
return path + File.separator + classPath + ".class";
}
/**
* 重写findClass方法,通过landClass访问
*
* @param name 类的全限定名称
* @return 返回Class对象
* @throws ClassNotFoundException
*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
this.classPath = name;
byte[] classData = null;
//读取字节码文件流
try (FileInputStream fileInputStream = new FileInputStream(this.getClassPath());
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();) {
int ch = 0;
while ((ch = fileInputStream.read()) != -1) {
byteArrayOutputStream.write(ch);
}
classData = byteArrayOutputStream.toByteArray();
} catch (IOException e) {
throw new ClassNotFoundException(name);
}
/**
* 参数一是类的全限定名称
* 参数二是字节码
* 参数三是读取字节码的开始偏移量
* 参数四是读取字节码的结束偏移量
* 这里表示读取全部字节码
*/
return defineClass(name, classData, 0, classData.length);
}
}
重写的java.lang.String
package java.lang;
public class String {
public String toString() {
return "新定义的String";
}
}
编译指令:
javac -d .\class -encoding UTF-8 .\java\lang\String.java
测试方法:
package com.main.jvmtexst.classloadertest.test2;
public class ParentalMechanismTest {
public static void main(String[] args) throws Exception {
//设置读取目录
String path = "D:\\selfProjects\\Test\\JAVABASIC\\class\\";
//创建自定义加载器
//使用默认加载器
ClassLoader classLoader1 = new CustomizeClassLoader(path);
//自定义加载器
ClassLoader classLoader2 = new CustomizeClassLoader(classLoader1, path);
System.out.println("classLoader1的加载器 : " + classLoader1.getParent());
System.out.println("classLoader2的加载器 : " + classLoader2.getParent());
//测试加载指定目录下的类文件
//加载并获取指定类的Class对象
Class loader = classLoader2.loadClass("java.lang.String");
System.out.println("核心库String的加载器 : " + String.class.getClassLoader());
//通过反射创建实例对象并获取他的Class加载器
System.out.println("自定义String的加载器 : " + loader.newInstance().getClass().getClassLoader());
}
}
运行结果:
classLoader1的加载器 : sun.misc.Launcher$AppClassLoader@18b4aac2
classLoader2的加载器 : com.main.jvmtexst.classloadertest.test2.CustomizeClassLoader@1622f1b
核心库String的加载器 : null
核心库String的Class : 1490180672
自定义String的加载器 : null
自定义String的Class : 1490180672
通过运行结果可以知道两种方式获取的String的加载器都是引导类加载器,同时他们的Class对象是同一个,也就说自定义的String没有加载,原因就是双亲机制,核心库的String先被加载,然后加载自定义的,但是双亲机制一直到引导类加载器,发现已经被加载过了,于是不加载了,直接返回加载好的,核心库的String的Class对象,这样我的篡改就失败了,这就保障了核心库API的安全性了。