一. 类加载过程
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 statsPerfCounter.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;}@Overrideprotected 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