JAVA类加载机制

JAVA类加载机制

流程

image-20251116155501990

也就是说在使用的时候会执行一些代码

(反序列化就是readobject代码自动加载)

类加载器

介绍

赋予了 Java 类可以被动态加载到 JVM 中并执行的能力

  • 类加载器是一个负责加载类的对象,用于实现类加载过程中的加载这一步
  • 每个 Java 类都有一个引用指向加载它的 ClassLoader
  • 数组类不是通过 ClassLoader 创建的(数组类没有对应的二进制字节流),是由 JVM 直接生成的
1
2
3
4
5
6
7
8
9
class Class<T> {
  ...
  private final ClassLoader classLoader;
  @CallerSensitive
  public ClassLoader getClassLoader() {
     //...
  }
  ...
}

主要作用就是动态加载 Java 类的字节码( .class 文件)到 JVM 中(在内存中生成一个代表该类的 Class 对象)。

加载规则

JVM 启动的时候,并不会一次性加载所有的类,而是根据需要去动态加载(调用才加载)

对于已经加载的类会被放在 ClassLoader 中。在类加载的时候,系统会首先判断当前类是否被加载过。已经被加载的类会直接返回,否则才会尝试加载。也就是说,对于一个类加载器来说,相同二进制名称的类只会被加载一次

1
2
3
4
5
6
7
8
9
10
11
public abstract class ClassLoader {
  ...
  private final ClassLoader parent;
  // 由这个类加载器加载的类。
  private final Vector<Class<?>> classes = new Vector<>();
  // 由VM调用,用此类加载器记录每个已加载类。
  void addClass(Class<?> c) {
        classes.addElement(c);
   }
  ...
}

代码块

静态代码块

static {},类第一次加载到 JVM 时执行一次。只执行一次!

用途:

  • 初始化类的静态资源(静态变量)
  • 只需要执行一次的准备动作(如加载配置文件、注册驱动等)

特点:

  • 只执行一次
  • 最早执行(甚至早于 main)
1
2
3
4
5
public class Demo {
    static {
        System.out.println("静态代码块:类加载时执行一次");
    }
}

构造代码块(实例代码块)

{ },每创建一个对象时执行一次,而且执行顺序在构造方法之前。

用途:

  • 多个构造方法间共享代码(避免重复)
1
2
3
4
5
6
7
8
9
public class Demo {
    {
        System.out.println("构造代码块:每次 new 对象时都会执行");
    }

    public Demo(){
        System.out.println("构造函数:最后执行");
    }
}

构造函数(构造方法)

每创建一个对象时执行一次,在构造代码块之后。

用途:

  • 初始化对象的成员变量
  • 创建对象时执行特定逻辑
1
2
3
public Demo() {
    System.out.println("构造方法执行!");
}

对比

名称 触发条件 执行次数 目的
静态代码块 static {} 类加载时 一次 初始化静态资源
构造代码块 {} 创建对象 每次 new 都执行 构造方法前统一代码
构造方法 创建对象 每次 new 都执行 初始化对象属性

Class.forName()

  • 用来 手动加载类 的方法

    Class.forName() 会触发:类加载 + 类初始化(执行静态代码块)

  • 跟进查看原因:

    • image-20251115221659426

      在forName调用的是forName0方法

    • 看forName0`方法

      image-20251115221822053

      native意思是c/c++写的

      传参:类名,判断(是否初始化),类加载器,其他(这个参数传递的都是null,不用写)

    • image-20251115222737321

      forname的重写,判断有没有初始化

  • 查看默认

    image-20251115223127503

    默认是true,也就是说默认是初始化的

    所以:

    1
    2
    ClassLoader cl =ClassLoader.getPlatformClassLoader();
           Class.forName("com.example.demo.Person");

    手动修改,不会默认进行初始化

    1
    2
    3
    ClassLoader cl =ClassLoader.getPlatformClassLoader();
          Class.forName("com.example.demo.Person");
          c.newInstance

    即可初始化

    c.newInstance用于创建一个类的实例,相当于调用它的 无参构造方法。

研究底层

双亲委派

https://javaguide.cn/java/jvm/classloader.html#%E5%8F%8C%E4%BA%B2%E5%A7%94%E6%B4%BE%E6%A8%A1%E5%9E%8B

用于确定使用哪个加载类的

image-20251116154159173

不是继承关系,是逻辑上的流程指向

而且这三个就加载器,所加载的路径不同

  • Bootstrap ClassLoader(启动类加载器):加载 $JAVA_HOME/jre/lib 目录下的核心类库(如 rt.jar、resources.jar 等),也可通过 -Xbootclasspath 参数指定路径。
  • Extension ClassLoader(扩展类加载器):加载 $JAVA_HOME/jre/lib/ext 目录下的类,或通过系统属性 java.ext.dirs 指定的路径。
  • Application ClassLoader(应用程序类加载器):加载 classpath 路径下的类,包括项目的 src/main/classes、lib 目录下的 jar 包,或通过 -cp/-classpath 参数指定的路径。

ClasshLoader

  • loadclass不进行初始化

  • 跟进去看看

    image-20251118093549650

    先调用一个参数到loadclass,返回了两个参数的loadclass

  • 跟进loadclass( —- , —–)

    image-20251118094001550

  • 到findclass()

    image-20251118094440081

    image-20251118203017193

    是相当于两者都是继承了urlclassload(其他两个没到找到findclass,所以就直接去了父类)

    image-20251118203448005

    而且目前已经生成了path(对于person)

    从路径中寻找,找到了之后调用了defineclass类

  • 步入defineclass

    image-20251118205224297

    用于安全判断

    但是后面的返回值还调用了一个defineclass()

    image-20251118205327222

  • 到SecureClassLoader.java(是urlclassload的父类)

    image-20251118205631243

  • 接下来

    image-20251118221037684

    调用了defindclass1

    image-20251118221126615

  • image-20251118221522174

    通过传入类名,以及编译之后的字节码,利用字节码直接将类搞出来

流程:

​ 继承:ClassLoader->SecureClassLoader->URLClassLoader->AppClassLoader

​ 执行:loadClass->findClass(重写的方法)->defineClass(从字节码加载类)

问题:

test里面,静态写入获取计算器的命令执行代码:执行完成之后,删除test类,在主类里用urlclassloader.loadclass(Test),为什么还会弹出计算器???

因为:只要类已经被加载到 JVM 中,其静态代码块就会执行,且不会因后续文件删除而撤销执行结果。

总结:

  1. 类加载与反序列化类加载的时候会执行代码
    初始化:静态代码块
    实例化:构造代码块、无参构造函数

  2. 动态类加载方法
    Class.forname
    初始化/不初始化
    ClassLoader.loadClass不进行初始化
    底层的原理,实现加载任意的类

  3. 漏洞利用

    URLClassLoader任意类加载:file/http/jar

    ClassLoader.defineClass 字节码加载任意类 私有

    Unsafe.defineClass 字节码加载 public 类不能直接生成 Spring 里面可以直接生成


JAVA类加载机制
http://example.com/2025/11/19/JAVA类加载机制/
作者
Piggy Sprint
发布于
2025年11月19日
许可协议