一、ClassLoader 定义
ClassLoader的作用就是根据一个指定的类的全限定名,找到对应的Class字节码文件,然后加载它转化成一个java.lang.Class类的一个实例。
所有Class都是由classloader进行加载的,ClassLoader负责通过将Class文件里的二进制数据流装载进系统,然后交给java虚拟机进行连接、初始化等操作。
JVM 运行实例中会存在多个 ClassLoader,不同的 ClassLoader 会从不同的地方加载字节码文件。它可以从不同的文件目录加载,也可以从不同的 jar 文件中加载,也可以从网络上不同的静态文件服务器来下载字节码再加载。
二、分类
BootStrap ClassLoader
BootStrap ClassLoader 是Java类加载层次中最顶层的类加载器,负责加载JDK中的核心类库,如:rt.jar、resources.jar、charsets.jar 和 java.lang 包下的文件等。由C++ 编写,不能被java程序直接调用,是虚拟机的一部分。
Extendsion ClassLaoder
加载扩展库 javax.* ; 主要加载\bin\ext 下的类库。
由java编写,可以把自己写的包放进去,也就是说开发者可以直接使用这个类加载器。
App ClassLoader
加载用户类路径(CLASSPATH)下的类库,用户自己的代码以及第三方jar包通常由APP ClassLoader 来加载。一般情况下这就是系统默认的类加载器。
自定义 ClassLoader
用户可自定义 ClassLoader 只要传入defineClass的二进制流是合法的,就可以通过不同形式去加载。如访问远程网络获取二进制流。
通过重写findClass方法可以对加密过的class进行解密、实现字节码增强技术、
ASM 等。
三、双亲委派机制
3.1、类加载器的层次
首先明确一个类加载器的层次结构,ClassLoader并没有继承其他类,但有一个 parent属性记录了当前类加载器的父类加载器
自定义 ClassLoader 的父类加载器是 App ClassLoader
App ClassLoader 的父类加载器是 Extendsion ClassLaoder
Extendsion ClassLaoder 没有父类加载器,它的 parent 是 null
public abstract class ClassLoader {
private static native void registerNatives();
static {
registerNatives();
}
// 委托的父类加载器
// 注意:VM硬编码了这个字段的偏移量,因此所有的新字段都必须添加“after”
private final ClassLoader parent;
// 类加载器的名字
private final String name;
3.2、双亲委派机制
当需要加载某个类时,类加载器不会第一时间进行加载,而是查看父类是否加载过该类。如果父类已经加载过这个类,就直接使用该类不再加载,如果父类不曾加载过该类,就检查父类的父类是否加载过。
当检查到 BootStrap ClassLoader 都没有加载过该类的话,由 BootStrap ClassLoader尝试加载该类,如果加载失败就交给 BootStrap ClassLoader 的子类尝试加载。若任加载失败,就交给子类的子类进行加载以此类推。
ClassLoader 中的 loadClass 方法
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// 为了避免多个线程同时进行加载造成重复加载一个类,所以在这里加一个同步锁
synchronized (getClassLoadingLock(name)) {
// 首先,检查类是否已经加载
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
// 检查当前类加载器的 parent 是否为空
if (parent != null) {
// parent 不为空让 parent调用此方法
c = parent.loadClass(name, false);
} else {
// parent 为空就让 BootStrap ClassLoader去尝试加载
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// 如果没有找到类,则抛出ClassNotFoundException
// 异常来自非空父类加载器
}
if (c == null) {
// 如果仍未找到,则按顺序调用findClass,去查找类
long t1 = System.nanoTime();
c = findClass(name);
// 这是定义类加载器,并记录数据
PerfCounter.getParentDelegationTime().addTime(t1 - t0);
PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
为了避免重复加载,当父亲已经加载了该类的时候,就没有必要 ClassLoader再加载一次。考虑到安全因素,我们试想一下,如果不使用这种委托模式,那我们就可以随时使用自定义的String来动态替代java核心api中定义的类型,这样会存在非常大的安全隐患,而双亲委托的方式,就可以避免这种情况,因为String已经在启动时就被引导类加载器(Bootstrcp ClassLoader)加载,所以用户自定义的ClassLoader永远也无法加载一个自己写的String,除非你改变JDK中ClassLoader搜索类的默认算法。
四、自定义一个类加载器
提前在桌面建一个 Wali.java 文件,并编译出 Wali.class 文件待用。
public class Wali{
static{
System.out.println("Wali");
}
}
package test;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class myClassLoader extends ClassLoader {
private String path;
private String className;
public myClassLoader(String path, String className) {
this.path = path;
this.className = className;
}
// 寻找类文件
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] b = loadClassData(name);
return defineClass(name, b, 0, b.length);
}
// 加载类文件
public byte[] loadClassData(String name) {
name = path + name + ".class";
InputStream in = null;
ByteArrayOutputStream out = null;
try {
in = new FileInputStream(new File(name));
out = new ByteArrayOutputStream();
int i = 0;
while((i = in.read()) != -1) {
out.write(i);
}
}catch (Exception e) {
e.printStackTrace();
}finally {
try {
out.close();
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return out.toByteArray();
}
}
package test;
public class ClassLoaderChecker {
@SuppressWarnings("all")
public static void main(String[] args)
throws ClassNotFoundException, InstantiationException, IllegalAccessException {
myClassLoader myClass = new myClassLoader("C:\\Users\\Administrator\\Desktop\\", "Wali.class");
Class c = myClass.loadClass("Wali");
System.out.println(c.getClassLoader());
c.newInstance();
}
}