Java 类加载器
一个类要得以运行,必须要被类加载器加载到内存中,那么什么是类加载器呢?学习一下
1. 类加载器(ClassLoader)的定义
顾名思义,类加载器就是将Java类从其它位置(如硬盘,网络等)加载到内存中的工具,JVM中可以有多个类加载器,系统默认的类加载器有:BootStrap、ExtClassLoader、AppClassLoader,它们分别负责加载特定位置的类;当然了,类加载器也是一个类。
1) BootStrap(引导类加载器)
负责加载JRE目录下的rt.jar文件里的Java系统核心类库
这个类加载器比较特殊,它不是一个Java类,它是嵌套在Java虚拟机内核中的,它不需要被别的类加载。
2) ExtClassLoader
负责加载加载/jre/lib/ext/目录下的扩展类库
3) AppClassLoader
负责加载加载环境变量CLASSPATH路径下的类
其中的MyClassLoader都是自定义加载器
2. 类加载的过程
Java类的加载分为三个过程,分别是:加载(Load)、链接(Link)和初始化(Initialize),其中链接又分为三个步骤,如下图所示:
这三个步骤分别干了些什么呢?
1) 装载:
查找并加载类的二进制数据。
2) 链接:
a) 验证:确保被加载类的正确性;
b) 准备:为类的静态变量分配内存,并将其初始化为默认值;
c) 解析:把类中的符号引用转换为直接引用。
3) 初始化:
为类的静态变量赋予正确的初始值。
3. 类加载器的委托机制
当一个类要被Java虚拟机加载时,:
1) 首先当前线程的类加载器会去加载线程中的第一个类;
2) 如果类A中引用了类B,Java虚拟机将使用加载类A的类加载器来加载类B;
3) 还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类;
4) 每个类加载器加载类时,又先委托给其上级类加载器,当所有父类加载器都没有加载到类时,则返回到发起者类加载器,还加载不了,则抛出ClassNotFoundException异常。
委托机制如图所示
4. 类的初始化
类的初始化情况:
1) 创建类的实例,也就是new一个对象
2) 访问某个类或接口的静态变量,或者对该静态变量赋值
3) 调用类的静态方法
4) 反射(Class.forName("com.lyj.load"))
5) 初始化一个类的子类(会首先初始化子类的父类)
6) JVM启动时标明的启动类,即文件名和类名相同的那个类
只有这6中情况才会导致类的类的初始化。
类的初始化步骤:
1) 如果这个类还没有被加载和链接,那先进行加载和链接
2) 假如这个类存在直接父类,并且这个类还没有被初始化(注意:在一个类加载器中,类只能初始化一次),那就初始化直接的父类(不适用于接口)
3) 加入类中存在初始化语句(如static变量和static块),那就依次执行这些初始化语句。
说了那么多的概念,下面来写一段代码说明一下:
测试BootStrap、ExtClassLoader、AppClassLoader的加载路径:
package learn.classloader;
import java.net.URL;
import java.net.URLClassLoader;
/*
分析BootstrapClassLoader/ExtClassLoader/AppClassLoader的加载路径
*/
public class ClassLoaderTest
{
public static void main(String[] args)
{
System.out.println("BootstrapClassLoader的加载路径:");
URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();
for(URL url : urls)
System.out.println(url);
//取得扩展类加载器
URLClassLoader extClassLoader =(URLClassLoader)ClassLoader.getSystemClassLoader().getParent();
System.out.println(extClassLoader);
System.out.println("扩展类加载器 的加载路径: ");
urls = extClassLoader.getURLs();
for(URL url : urls)
System.out.println(url);
//取得应用(系统)类加载器
URLClassLoader appClassLoader =(URLClassLoader)ClassLoader.getSystemClassLoader();
System.out.println(appClassLoader);
System.out.println("应用(系统)类加载器 的加载路径: ");
urls = appClassLoader.getURLs();
for(URL url : urls)
System.out.println(url);
}
}
程序输出:
BootstrapClassLoader 的加载路径:
file:/C:/Application/JDK/jre/lib/resources.jar
file:/C:/Application/JDK/jre/lib/rt.jar
file:/C:/Application/JDK/jre/lib/sunrsasign.jar
file:/C:/Application/JDK/jre/lib/jsse.jar
file:/C:/Application/JDK/jre/lib/jce.jar
file:/C:/Application/JDK/jre/lib/charsets.jar
file:/C:/Application/JDK/jre/lib/jfr.jar
file:/C:/Application/JDK/jre/classes
sun.misc.Launcher$ExtClassLoader@15db9742
扩展类加载器 的加载路径:
file:/C:/Application/JDK/jre/lib/ext/access-bridge-64.jar
file:/C:/Application/JDK/jre/lib/ext/cldrdata.jar
file:/C:/Application/JDK/jre/lib/ext/dnsns.jar
file:/C:/Application/JDK/jre/lib/ext/jaccess.jar
file:/C:/Application/JDK/jre/lib/ext/jfxrt.jar
file:/C:/Application/JDK/jre/lib/ext/localedata.jar
file:/C:/Application/JDK/jre/lib/ext/nashorn.jar
file:/C:/Application/JDK/jre/lib/ext/sunec.jar
file:/C:/Application/JDK/jre/lib/ext/sunjce_provider.jar
file:/C:/Application/JDK/jre/lib/ext/sunmscapi.jar
file:/C:/Application/JDK/jre/lib/ext/sunpkcs11.jar
file:/C:/Application/JDK/jre/lib/ext/zipfs.jar
sun.misc.Launcher$AppClassLoader@73d16e93
应用(系统)类加载器 的加载路径:
file:/F:/Java/Learn/bin/
这个测试很好的体现了三个系统类加载器的加载机制
5. 自定义类加载器
1) 自定义加载器原理分析:
类加载器中的loadClass方法内部实现了父类委托机制,因此我们没有必要自己覆盖loadClass,否则需要自己去实现父类委托机制。我们只需要覆盖findClass方法。loadClass方法中调用了findClass方法,使用的是模板设计模式。我们得到了Class文件后,就可以通过defineClass方法将二进制数据转换成字节码。加载过程中会先检查类是否被已加载,检查顺序是自底向上,从自定义类加载器(Custom ClassLoader)到BootStrap逐层检查,只要某个classloader已加载就视为已加载此类,保证此类只所有ClassLoader加载一次,而加载的顺序是自顶向下,也就是由上层来逐层尝试加载此类,这就是自定义类加载器的编写原理。
下面我们通过代码来自己写一个类加载器对类进行加密和解密:
加密解密(因为是对二进制数进行取反操作,加密函数encrypt()既可以加密也可以解密):
package learn.classloader;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
public class MyClassLoader extends ClassLoader {
/**类目录*/
private String classDir;
public MyClassLoader(String classDir) {
this.classDir = classDir;
}
/**加密算法*/
public static void encrypt(InputStream ips, OutputStream ops)
throws Exception {
int by = 0;
while ((by = ips.read()) != -1) {
/*对每一位二进制取反,进行加密*/
ops.write(by ^ 0xff);
}
}
@SuppressWarnings("deprecation")
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException{
String classFileName = classDir + "\\"
+ name.substring(name.lastIndexOf(".") + 1) + ".class";
try {
/*获取class文件二进制流*/
InputStream ips = new FileInputStream(classFileName);
ByteArrayOutputStreambos = newByteArrayOutputStream();
/*加密*/
encrypt(ips, bos);
ips.close();
byte[] bytes = bos.toByteArray();
return defineClass(bytes, 0, bytes.length);
} catch (Exception e) {
e.printStackTrace();
}
return super.findClass(name);
}
/**测试*/
public static void main(String[] args) throws Exception {
String srcPath = args[0];
String destPath = args[1]
+ "\\"
+ srcPath.substring(srcPath.lastIndexOf("\\") + 1,
srcPath.length());
InputStream ips = new FileInputStream(srcPath);
OutputStream ops = new FileOutputStream(destPath);
/*加密*/
encrypt(ips, ops);
ips.close();
ops.close();
/*创建类实例
MyClassLoader myClassLoader=newMyClassLoader("");
Class clazz=myClassLoader.findClass("");
clazz.newInstance();*/
}
}
关于类加载器就见到这里。