类的加载

类加载

Java类加载的三个阶段

  1. 加载(Loading)
    把类的字节码读进内存,创建Class对象。
  2. 链接(Linking)
    验证字节码、准备静态变量的内存空间(默认赋初值)。
  3. 初始化(Initialization)
    执行类的静态代码块和静态变量的显式赋值。

Java 类加载器(ClassLoader)负责查找和加载类的字节码到 JVM 中。它们按照一定的顺序和规则进行加载,以确保类的唯一性和安全性。

类加载机制

类加载与反序列化

  • 类加载时,JVM会执行类的静态代码块(初始化阶段)
  • 实例化对象时会执行构造代码块和无参构造函数(实例化阶段)
  • 反序列化通常会触发类加载,因此静态代码块可能会被执行,这也是反序列化安全隐患的一个重要原因

动态类加载方法

  • Class.forName() 可以加载类,并且默认会执行类的初始化(静态代码块,可以选择初始化和不初始化
  • ClassLoader.loadClass() 只加载类但不初始化,即不会执行静态代码块,通常需要调用 Class.forName 或手动初始化才能执行静态代码块
代码块类型 什么时候执行
静态代码块(static) 类加载时执行,只执行一次
构造代码块 每次创建对象时执行
构造方法 每次创建对象时执行,紧跟实例初始化块后
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Evil {
static {
System.out.println("Evil类的静态代码块执行了!");
}
{
System.out.println("dis");
}

public void hello() {
System.out.println("Evil.hello() 被调用了!");
}

public static void main(String[] args) {
Evil evil = new Evil();
evil.hello();
evil.hello();
}
}

image-20250809220004318

加载类和实例化类的区别

加载类

把类的字节码从文件(比如 .class 文件)或者其他地方读进内存,JVM 会为这个类创建一个 Class 对象。

实例化类

根据加载好的类,创建该类的一个对象(实例)。

Class.forName(String className)和ClassLoader.loadClass(String className)的区别

Class.forName(String className)

加载指定类,并且会对类进行初始化(执行静态代码块)。

ClassLoader.loadClass(String className)

加载指定类,但不会执行初始化,只是把类加载到JVM中。

加载方式

URLClassLoader → 从 URL 位置(文件、HTTP、JAR)加载现成的类文件。

ClassLoader.defineClass → 用字节数组加载类(需要继承 ClassLoader 调用)。

Unsafe.defineClass → 直接用字节数组加载类(更底层,但 public 类有包名限制)。

ClassLoader的分类

  • Bootstrap ClassLoader:这是最顶层的类加载器,通常由本地代码实现,负责加载位于<JAVA_HOME>/lib目录下的核心Java库。
  • Extension ClassLoader:作为Bootstrap ClassLoader的子类加载器,它负责加载扩展库中的类,即位于<JAVA_HOME>/lib/ext目录下的类。
  • Application ClassLoader:也称为System ClassLoader,它是Extension ClassLoader的子类加载器,负责加载应用类路径上指定的所有类(包括用户定义的类和第三方库)。
  • Custom ClassLoader:开发者可以根据需要创建自己的类加载器,这些自定义类加载器通常继承自java.lang.ClassLoader,并可用于从非标准位置加载类,支持动态加载、热替换等功能。

ClassLoader的加载机制

1
2
3
4
5
6
7
8
9
10
public class Main {
public static void main(String[] args) {
ClassLoader ClassLoader1 = Main.class.getClassLoader();
ClassLoader ClassLoader2 = ClassLoader1.getParent();
ClassLoader ClassLoader3 = ClassLoader2.getParent();

System.out.println(ClassLoader1);
System.out.println(ClassLoader2);
System.out.println(ClassLoader3); }
}

当这三者中的某个ClassLoader要加载一个类时,会先委托它的父类加载器尝试加载,一直往上,如果最顶级的父类加载器没有找到该类,那么委托者则亲自到特定的地方加载

image-20251204192559942

Java 类在使用前必须被编译到类路径下,JVM 通过双亲委派机制的类加载器,从类路径中加载字节码,完成类的加载、链接和初始化。

  1. 请求到达App ClassLoader
    • 当应用程序需要加载某个类时,首先会将这个请求传递给应用程序类加载器(App ClassLoader)。
  2. 委托给Extension ClassLoader
    • App ClassLoader不会立即尝试自己加载这个类,而是先将这个请求委托给它的父类加载器——扩展类加载器(Extension ClassLoader)。
  3. 委托给Bootstrap ClassLoader
    • Extension ClassLoader同样不会立即尝试自己加载这个类,而是继续将请求委托给它的父类加载器——启动类加载器(Bootstrap ClassLoader)。
  4. Bootstrap ClassLoader尝试加载
    • Bootstrap ClassLoader检查是否能在其负责的范围内找到并加载该类(即在核心Java库中查找)。如果找到了,则完成加载;如果没有找到,则返回“未找到该类”的结果。
  5. 回退到Extension ClassLoader
    • 如果Bootstrap ClassLoader未能找到该类,控制权会回到Extension ClassLoader。此时,Extension ClassLoader会在其负责的范围内(即扩展库目录)查找并尝试加载该类。如果找到了,则完成加载;如果没有找到,则再次返回“未找到该类”的结果。
  6. 最终由App ClassLoader加载
    • 如果Bootstrap ClassLoader和Extension ClassLoader都无法加载该类,那么最后的尝试就落在了App ClassLoader身上。App ClassLoader会在用户定义的类路径上查找并加载该类。如果在这个过程中找到了该类,则完成加载;如果仍然找不到,则会抛出ClassNotFoundException异常。

自定义ClassLoader

1、为什么要自定义ClassLoader

因为系统的ClassLoader只会加载指定目录下的class文件,如果你想加载自己的class文件,那么就可以自定义一个ClassLoader.

而且我们可以根据自己的需求,对class文件进行加密和解密。

2.如何自定义ClassLoader

  • 新建一个类继承自java.lang.ClassLoader,重写它的findClass方法。
  • 将class字节码数组转换为Class类的实例
  • 调用loadClass方法

类的加载
http://xiaowu5.cn/2025/12/04/类的加载/
作者
5
发布于
2025年12月4日
许可协议
BY XIAOWU