JVM双亲委派与自定义类加载器
一. 类加载过程
Java Application运行前需要将编译生成的字节码文件加载到JVM中,JVM类加载过程如下:
1. 加载
加载阶段是类加载的第一步,在加载阶段JVM会查找并加载类的字节码文件,这个过程通常从类路径(Classpath)中查找类文件,然后将它们读入内存。
2. 验证
一旦类被加载到内存中,JVM会对字节码文件进行验证,以确保其完整性和合法性。
-
文件格式验证
在这个阶段,JVM首先检查字节码文件的格式是否合法。这包括检查文件头是否以魔数开头(通常为0xCAFEBABE),以及文件版本号是否合适。 -
语义验证
在这个阶段JVM会对字节码进行语义分析,确保class文件中不会存在语法错误和语义错误。 -
字节码验证
这个是最复杂的一步,它检查字节码是否符合Java语言规范。这包括验证操作码是否合法,跳转指令是否正确,以及栈操作是否匹配。如果字节码验证失败,JVM会认为这个类是不安全的,并拒绝加载它
3. 准备
Java虚拟机的类准备阶段是类加载过程的重要步骤之一,它负责为类的静态变量分配内存并初始化这些变量。
4. 解析
解析阶段的主要任务:是将类或接口中的符号引用转化为直接引用。
解析过程包括以下步骤:
- 根据符号引用的类名找到对应的类。
- 验证类的可访问性和继承关系,确保访问不会违反访问控制规则。
- 找到符号引用对应的字段或方法,获取其内存地址或偏移量。
- 最终将符号引用替换为直接引用,以便在运行时直接访问类,字段或方法。
5. 初始化
初始化阶段是类加载的最后一步,它负责执行类的初始化代码。在初始化阶段,静态代码块会被执行,静态变量会被赋予初始值。
二. ClassLoader的分类
在标准的Java程序中,JVM会创建3个ClassLoader为应用程序服务。它们分别是BootStrap ClassLoader(启动类加载器),Extension ClassLoader(扩展类加载器),AppClassLoader(应用类加载器)。
- 启动类是c++实现的,主要用于加载系统的核心类,比如rt.jar中的Java类。
- 扩展类加载器用于加载%JAVA_HOME%/lib/ext/*.jar中的Java类。
- 应用类加载器用于加载用户类。
import sun.net.spi.nameservice.dns.DNSNameService;
public class ClassLoaderDemo {
public static void main(String[] args) {
// 启动类加载器
ClassLoader classLoader1 = Object.class.getClassLoader();
System.out.println("Object类加载器: " + classLoader1);
// ext加载器
System.out.println("========================================");
ClassLoader classLoader2 = DNSNameService.class.getClassLoader();
System.out.println("DNSNameService类加载器: " + classLoader2);
// system加载器
System.out.println("========================================");
ClassLoader classLoader3 = ClassLoaderDemo.class.getClassLoader();
System.out.println("ClassLoaderDemo类加载器: " + classLoader3);
}
}
运行结果:
Object类加载器: null
========================================
DNSNameService类加载器: sun.misc.Launcher$ExtClassLoader@6f75e721
========================================
ClassLoaderDemo类加载器: sun.misc.Launcher$AppClassLoader@18b4aac2
双亲委派模型
JVM进行类加载时,系统会判断当前类是否被加载,如果被加载则会返回已经被加载的类;如果没有被加载则会默认先请求双亲(启动类加载器和扩展类加载器)进行加载,如果加载不成功,则会自己加载。
双亲委派的作用:
-
安全性,确保程序安全,防止核心API被随意篡改。比如自己写的java.lang.String.class类是不会被加载的。
-
避免重复加载,保证被加载类的唯一性。
下面用一段代码来演示双亲委派机制。
public class ClassLoaderDemo {
public static void main(String[] args) {
// 双亲委派
ClassLoader classLoader1 = ClassLoaderDemo.class.getClassLoader();
while (classLoader1 != null) {
System.out.println(classLoader1);
classLoader1 = classLoader1.getParent();
}
}
}
运行结果:
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@2d363fb3
双亲委派源码实现在ClassLoader类中,ClassLoader类的核心方法如下:
loadClass方法源码:
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
//加synchronized,防止多线程下重复加载
synchronized (getClassLoadingLock(name)) {
// 先检查类是否已被加载,findLoadedClass往下跟是调用native方法
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
//类加载器的parent属性不为空,即有父加载器
if (parent != null) {
//自己调自己,这里体现的是向上查找
c = parent.loadClass(name, false);
} else {
//去启动类加载器里找,往下跟是native方法
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
//三个加载器用完了,c还是为空
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
//那就调用findClass方法,它是LoadClass抽象类的空方法,给子类去实现,这是自定义类加载器的切入点和扩展点
c = findClass(name);
// this is the defining class loader; record the stats
PerfCounter.getParentDelegationTime().addTime(t1 - t0);
PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
PerfCounter.getFindClasses().increment();
}
}
//resolve为false,则不执行resolveClass方法,即不要类生命周期里的连接阶段
if (resolve) {
resolveClass(c);
}
return c;
}
}
三. 自定义类加载器
当JVM自带的类加载器不能满足需求时,可以自定义ClassLoader,自定义ClassLoader需要继承ClassLoader基类,并重写其中的方法,以实现对类加载过程的自定义控制。
1. 准备一个被加载的类
在D盘下新建一个Demo.java文件
package com.mooc.test;
public class Demo{
public Demo(){
System.out.println("demo instance");
}
}
将Demo.java文件编译成class文件
2. 自定义类加载器重写loadclass方法
package basic;
import java.io.*;
public class MyFileClassLoader extends ClassLoader {
private String directory;
public MyFileClassLoader(String directory) {
this.directory = directory;
}
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
// 非测试类使用父加载器
if(! name.startsWith("com.mooc.test")) {
return super.loadClass(name, resolve);
}
byte[] data = null;
try {
// 把类名转换为目录
String file = directory + File.separator + name.replace(".", File.separator) + ".class";
// 构建输入流
InputStream in = new FileInputStream(file);
// 构建字节输出流
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = -1;
while ((len = in.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}
// 读取到的字节码的二进制数据
data = baos.toByteArray();
in.close();
baos.close();
return defineClass(name, data, 0, data.length);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) throws Exception {
MyFileClassLoader classLoader = new MyFileClassLoader("D:");
System.out.println("当前类加载器: " + classLoader);
System.out.println("当前类加载器的父加载器: " + classLoader.getParent());
Class clazz = classLoader.loadClass("com.mooc.test.Demo");
clazz.newInstance();
}
}
运行结果:
当前类加载器: basic.MyFileClassLoader@2d363fb3
当前类加载器的父加载器: sun.misc.Launcher$AppClassLoader@18b4aac2
demo instance