You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
139 lines
6.3 KiB
139 lines
6.3 KiB
9 months ago
|
---
|
||
|
title: java中的类加载器
|
||
|
tags: Java技术
|
||
|
categories: 技术随笔
|
||
|
abbrlink: 3639892104
|
||
|
date: 2024-03-06 16:22:05
|
||
|
keywords:
|
||
|
description:
|
||
|
cover:
|
||
|
---
|
||
|
> java中的类加载器主要有三种:
|
||
|
> 引导类加载器
|
||
|
> 扩展类加载器
|
||
|
> 应用类加载器
|
||
|
|
||
|
# 通过代码了解类加载器
|
||
|
|
||
|
```
|
||
|
public class TestJDKClassLoader {
|
||
|
public static void main(String[] args) {
|
||
|
//打印类加载器
|
||
|
System.out.println(String.class.getClassLoader()); //不是java对象,打印null
|
||
|
System.out.println(DESKeyFactory.class.getClassLoader());
|
||
|
System.out.println(TestJDKClassLoader.class.getClassLoader());
|
||
|
//类加载器之间的关系
|
||
|
System.out.println();
|
||
|
ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
|
||
|
ClassLoader extClassLoader = appClassLoader.getParent();
|
||
|
ClassLoader bootstrapLoader = extClassLoader.getParent();
|
||
|
System.out.println(appClassLoader);
|
||
|
System.out.println(extClassLoader);
|
||
|
System.out.println(bootstrapLoader);
|
||
|
|
||
|
System.out.println();
|
||
|
System.out.println("bootstrapLoader加载以下文件:");
|
||
|
URL[] urLs = Launcher.getBootstrapClassPath().getURLs();
|
||
|
for (URL url : urLs) {
|
||
|
System.out.println(url);
|
||
|
}
|
||
|
|
||
|
System.out.println();
|
||
|
System.out.println("extClassLoader加载以下文件:");
|
||
|
System.out.println(System.getProperty("java.ext.dirs"));
|
||
|
|
||
|
System.out.println();
|
||
|
System.out.println("appClassLoader加载以下文件");
|
||
|
System.out.println(System.getProperty("java.class.path"));
|
||
|
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
# 双亲委派机制
|
||
|
|
||
|
JVM在进行类加载时有个双亲委派机制,当它需要去加载类时,自己先不去加载,而是委托给父加载器加载,父加载器又直接委派它的父加载器,直到找到引导类加载器时,他才会去对类进行加载,这时候如果加载不到,也就是在它所管辖的范围内找不到这个类,就会进行委托的退回,说,啊,我不行,还是你自己来吧,这样一直退回到能加载这个类的时候,才会对类进行加载。
|
||
|
总结起来就是所有类都会优先使用最顶层的类加载器去加载,从而保证了系统的安全性和类加载的唯一性。
|
||
|
比如我是一个黑客,在用户的系统里写了一个String类,这个类里面做了一些危害系统的代码,这时候双亲委派机制就能有效的避过这个类,他是应用类加载器要加载String时,会委派给上层,上层在委派给上上层,一直到引导类加载器,引导类加载器会直接使用Java自带的String类。
|
||
|
|
||
|
# 全盘负责委托机制
|
||
|
|
||
|
“全盘负责”是指当一个ClassLoder装载一个类时,除非显式的使用另外一个ClassLoder,否则该类所依赖及引用的类也由这个ClassLoder载入
|
||
|
|
||
|
# 自定义类加载器
|
||
|
|
||
|
自定义类加载器只需要继承`java.lang.ClassLoader`类,该类有两个核心方法,一个是`loadClass(String, boolean)`,实现了双亲委派机制,还有一个方法是`findClass`,默认实现是空方法,所以我们自定义类加载器主要是重写`findClass`方法.
|
||
|
打破双亲委派机制主要就是通过重写`loadClass`方法,去掉它的递归委派的代码。
|
||
|
|
||
|
测试代码
|
||
|
|
||
|
```
|
||
|
public class MyClassLoaderTest {
|
||
|
static class MyClassLoader extends ClassLoader {
|
||
|
private String classPath;
|
||
|
public MyClassLoader(String classPath) {
|
||
|
this.classPath = classPath;
|
||
|
}
|
||
|
private byte[] loadByte(String name) throws Exception {
|
||
|
name = name.replaceAll("\\.", "/");
|
||
|
FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class");
|
||
|
int len = fis.available();
|
||
|
byte[] data = new byte[len];
|
||
|
fis.read(data);
|
||
|
fis.close();
|
||
|
return data;
|
||
|
}
|
||
|
protected Class<?> findClass(String name) throws ClassNotFoundException {
|
||
|
try {
|
||
|
byte[] data = loadByte(name);
|
||
|
// defineClass 将一个字节数组转为 Class 对象,
|
||
|
// 这个字节数组是 class 文件读取后最终的字节数组
|
||
|
return defineClass(name, data, 0, data.length);
|
||
|
} catch (Exception e) {
|
||
|
e.printStackTrace();
|
||
|
throw new ClassNotFoundException();
|
||
|
}
|
||
|
}
|
||
|
//双亲委派机制
|
||
|
//沙箱安全机制:自己写的java.lang.String.class类不会被加载,这样可以防止核心API库被随意篡改
|
||
|
//避免类的重复加载:当父亲已经加载了该类时,就没必要子classloader再加载一次,保证被加载类的唯一性。
|
||
|
//下面通过重写loadClass来打破双亲委派机制
|
||
|
protected Class<?> loadClass(String name, boolean resolve)
|
||
|
throws ClassNotFoundException {
|
||
|
synchronized (getClassLoadingLock(name)) {
|
||
|
// First, check if the class has already been loaded
|
||
|
Class<?> c = findLoadedClass(name);
|
||
|
if (c == null) {
|
||
|
if (!name.startsWith("com.locaris")) {
|
||
|
c = this.getParent().loadClass(name);
|
||
|
} else {
|
||
|
c = findClass(name);
|
||
|
}
|
||
|
sun.misc.PerfCounter.getFindClasses().increment();
|
||
|
}
|
||
|
if (resolve) {
|
||
|
resolveClass(c);
|
||
|
}
|
||
|
return c;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
}
|
||
|
public static void main(String args[]) throws Exception {
|
||
|
// 初始化自定义类加载器,会初始化父类 ClassLoader
|
||
|
// 其中会把自定义类加载器的父类加载器,设置为应用程序类加载器 AppClassLoader
|
||
|
MyClassLoader classLoader = new MyClassLoader("D:/test");
|
||
|
// D 盘创建 test/com/swordsman/jvm 几级目录,将 User 类的复制类 User1 丢入该目录
|
||
|
Class clazz = classLoader.loadClass("com.youren.jvm.User");
|
||
|
Object obj = clazz.newInstance();
|
||
|
Method method = clazz.getDeclaredMethod("sout", null);
|
||
|
method.invoke(obj, null);
|
||
|
|
||
|
// 这里因为双亲委派机制的原因,
|
||
|
// 如果 User1 也在 classpath 下,类加载器就是 AppClassLoader
|
||
|
// 只在 D:/test 下就是自定义加载器
|
||
|
System.out.println(clazz.getClassLoader().getClass().getName());
|
||
|
}
|
||
|
}
|
||
|
```
|