JAVA类加载机制
JAVA类加载机制
流程

也就是说在使用的时候会执行一些代码
(反序列化就是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()会触发:类加载 + 类初始化(执行静态代码块)跟进查看原因:

在forName调用的是
forName0方法看forName0`方法

native意思是c/c++写的
传参:类名,判断
(是否初始化),类加载器,其他(这个参数传递的都是null,不用写)
forname的重写,判断有没有初始化
查看默认

默认是true,也就是说默认是初始化的
所以:
1
2ClassLoader cl =ClassLoader.getPlatformClassLoader(); Class.forName("com.example.demo.Person");手动修改,不会默认进行初始化
1
2
3ClassLoader cl =ClassLoader.getPlatformClassLoader(); Class.forName("com.example.demo.Person"); c.newInstance即可初始化
c.newInstance用于创建一个类的实例,相当于调用它的 无参构造方法。
研究底层
双亲委派
用于确定使用哪个加载类的

不是继承关系,是逻辑上的流程指向
而且这三个就加载器,所加载的路径不同
- 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不进行初始化
跟进去看看

先调用一个参数到loadclass,返回了两个参数的loadclass
跟进loadclass( —- , —–)

到findclass()


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

而且目前已经生成了path(对于person)
从路径中寻找,找到了之后调用了defineclass类
步入defineclass

用于安全判断
但是后面的返回值还调用了一个defineclass()

到SecureClassLoader.java(是urlclassload的父类)

接下来

调用了defindclass1


通过传入类名,以及编译之后的字节码,利用字节码直接将类搞出来
流程:
继承:ClassLoader->SecureClassLoader->URLClassLoader->AppClassLoader
执行:loadClass->findClass(重写的方法)->defineClass(从字节码加载类)
问题:
test里面,静态写入获取计算器的命令执行代码:执行完成之后,删除test类,在主类里用urlclassloader.loadclass(Test),为什么还会弹出计算器???
因为:只要类已经被加载到 JVM 中,其静态代码块就会执行,且不会因后续文件删除而撤销执行结果。
总结:
类加载与反序列化类加载的时候会执行代码
初始化:静态代码块
实例化:构造代码块、无参构造函数动态类加载方法
Class.forname
初始化/不初始化
ClassLoader.loadClass不进行初始化
底层的原理,实现加载任意的类漏洞利用
URLClassLoader任意类加载:file/http/jar
ClassLoader.defineClass 字节码加载任意类 私有
Unsafe.defineClass 字节码加载 public 类不能直接生成 Spring 里面可以直接生成