类的动态加载

类的动态加载

回顾

  • 静态代码块在类加载时已经被加载了

    • 静态代码块用来初始化静态成员变量的,在类加载的初始化阶段执行
  • 构造代码块和构造函数都是在实例化对象时会被调用,构造代码块依赖于构造函数,先于构造函数执行

    • 构造代码块的作用也是和构造函数的一样,用于初始化对象,并且只要创建一个对象,构造代码块都会执行一次,但创建一个对象会自动选择调有参还是无参构造方法,构造方法不会全调

初始化:类的class不会触发初始化,Class的forName和Object的getClass会触发初始化

**实例化会触发…**:1.类加载初始化 2.构造代码块 3.(有参或无参)构造器 {就是静态代码块和构类的}

1
2
3
4
5
6
7
8
9
10
11
12
ClassLoader c1 = ClassLoader.getSystemClassLoader();
Class<?> c = Class.forName("com.test.classLoad.Person", false, c1);
c.newInstance();//创建一般对象触发构造代码块和无参构造器
System.out.println("=======");
c.getDeclaredConstructor(String.class,int.class).newInstance("xiao",18);//创建一般对象触发构造代码块和有参构造器
System.out.println("=======");
/*下面
*触发:类加载初始化-->静态代码块
* 实例化对象--->构造代码块and有参构造器
*/
System.out.println("即使程序中会有多个地方触发类加载初始化,但static代码块只执行一次噢");
Person person = new Person("wang",20);

image-20250410143734859


————–

forName()

forName()它可以选择触发或者不触发初始化:

image-20250409211313959

  • ClassLoader :抽象类

    它里面的静态方法getSystemClassLoader() 获得系统加载类

  • 当传参布尔值为false时,由Class.forName()创建类对象时不会触发初始化!!

1
2
ClassLoader c1 = ClassLoader.getSystemClassLoader();
Class.forName("com.test.classLoad.A",false,c1);

ClassLoader.loadClass()

它 不触发初始化,类加载器获取到Class类对象,并实例化

欸这不就是之前学类加载过程时那句话的具体实现嘛:程序编译成class文件后,通过类加载器加载Class对象到堆中。每一个加载到内存的类都由一个 Class 对象来表示每一个 Class 对象都有一个指向加载该类的类加载器的引用,通过 getClassLoader()方法就可以获取到此引用

1
2
3
4
ClassLoader cl = ClassLoader.getSystemClassLoader();//获得系统类加载器c1
Class<?> c = cl.loadClass("com.test.classLoad.Person");//!!!!这里loadClass方法获得Class对象是不会初始化的 也就与之前学的那句话相符
c.newInstance();
//到这儿肯定调的是static代码块,构造代码块,无参构造器

父类加载器

先了解一下父类加载器吧

  • 对于开发人员编写的类加载器来说

    • 其父类加载器是加载此类加载器 Java 类的类加载器。因为类加载器 Java 类如同其它的 Java 类一样,也是要由类加载器来加载的。调用 getParent()方法来输出父类加载器。一般来说,开发人员编写的类加载器的父类加载器是系统类加载器(AppClassLoader)。
  • 对于系统提供的类加载器来说

    • 系统类加载器(AppClassLoader)的父类加载器是扩展类加载器ExtClassLoader
    • 而扩展类加载器ExtClassLoader的父类加载器是启动类加载器BootstrapClassLoader;

Java虚拟机判断两个类是否相同除了要看类的全名是否相同还要看加载此类的类加载器是否相同

类加载的底层

不同版本的jdk是不一样的,下面按组长视频的jdk8学习具体的类加载器加载一个类到内存中,类加载底层实现(这里注意:调试的时候要设置一下取消勾选跳过加载器)

安装java8和java21共存,如何自由切换java版本?_java切换版本-CSDN博客

IDEA——修改开发环境为 JDK 1.8_javaweb项目jdk为1.8怎么修改-CSDN博客

下面的类加载学习参考文章:

好怕怕的类加载器 - 知乎

https://qchery.github.io/2019/10/11/

https://github.com/burningmyself/burningmyself.github.io/blob/master/docs/java/load-class.md

https://louluan.blog.csdn.net/article/details/50529868

image-20250410162826850

Launcher是JRE中用于启动程序入口main()的类,让我们看下Launcher的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public Launcher() {
Launcher.ExtClassLoader var1;
try {
//加载扩展类类加载器
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}

try {
//加载应用程序类加载器,并设置parent为extClassLoader
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}
//设置默认的线程上下文类加载器为AppClassLoader
Thread.currentThread().setContextClassLoader(this.loader);
//此处删除无关代码。。。
}

在虚拟机启动的时候会初始化BootstrapClassLoader,然后在Launcher类中去加载ExtClassLoader、AppClassLoader,并将AppClassLoader的parent设置为ExtClassLoader,并设置线程上下文类加载器。

类的加载工作

这块儿具体就是一个双亲委派机制~~

  1. 检查该类是否已经被当前的类加载器加载。因为一旦一个类被加载到JVM中,同一个类就不会被再次载入了。加载到内存中的Class对象是唯一的

image-20250410203627775

  1. 若已经加载过则直接返回对应的Class实例就好了

    若没加载,则就要委托给父加载器,现在是APP,则让父类加载器EXT加载(调用父类加载器的loadClass,还是这个代码)

    Ext没有(parent=null,实际上是等于Bootstrap ClassLoader,因为它是c写的所以在java中就是null),则看是已经被启动类加载器BootstrapClassLoader加载

    (下面的代码在AppClassLoader时走的是if后面的,ExtClassLoader时走的是else后面的)

image-20250410220807191

  1. !!!!!!现在还在Ext还没出去,上面的都没加载,则尝试该类加载器Ext自己加载,则调用 findClass() 方法加载类

    image-20250412151107691

  2. 调Ext的findClass(),但EXT和APP都没有findClass方法,就会调它的父类URLClassLoader的findClass了,没有找到,就退出回到APP的(c==null)了,继续调APP的findClass即调URLClassLoader的findclass—>URLClassLoader的defineClass—>SecureClassLoader defineClass—>ClassLoader defineClass

继承关系:ClassLoader->SecureClassLoader->URLClassLoader->AppClassLoader

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


————–

上面是类加载器的具体工作了,下面学习安全方面的类加载

URLClassLoader

任意类加载 file/http/jar

进行操作时在本地报错:ClassNotFoundException:Try02 (wrong name: com/test/classLoad/Try02·

http协议报错.NoClassDefFoundError: Try02 (wrong name: com/test/classLoad/Try02),但看服务是收到了请求的

image-20250412171220098

在快放下它的时候终于终于在一篇文章里知道原因了!!~~,因为编译的时候没有删除package全包名,(带上包名,URLClassLoader当然会从本地去加载指定class类了那肯定就找不到我们外部加载的类)

下面是它的加载代码

http协议:

1
2
3
URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{new URL("http://localhost:9999/")});
Class<?> c1 = urlClassLoader.loadClass("Test95");
c1.newInstance();//触发初始化

本地加载;

1
2
3
URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{new URL("file:///D:\\tmp\\classes\\")});
Class<?> c1 = urlClassLoader.loadClass("Test95");
c1.newInstance();

jar:

生成jar文件

  1. 先编译.java文件生成.class文件

  2. 在终端对应目录执行命令 jar cf myapp.jar MyClass.class(c 表示创建一个新的 JAR 文件。f 指定 JAR 文件的名称,这里是 myapp.jarMyClass.class 是要包含在 JAR 文件中的编译后的 Java 类文件。)

    image-20250412205821124

1
2
3
URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{new URL("jar:file:///D:\\tmp\\classes\\myapp.jar!/")});
Class<?> c1 = urlClassLoader.loadClass("Test95");//这里的时jar保存编译后的class文件
c1.newInstance();

jar包也可以用http协议

1
2
3
URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{new URL("jar:http://localhost:9595/myapp.jar!/")});
Class<?> c1 = urlClassLoader.loadClass("Test95");
c1.newInstance();

defineClass()

ClassLoader.defineClass 字节码加载任意类,defineClass是一个protected的方法,所以就得反射调用

1
2
3
4
5
6
ClassLoader cl = ClassLoader.getSystemClassLoader();
Method defineClassMethd = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
defineClassMethd.setAccessible(true);
byte[] code= Files.readAllBytes(Paths.get("D:\\tmp\\classes\\Test95.class"));
Class c = (Class) defineClassMethd.invoke(cl, "Test95", code, 0, code.length);
c.newInstance();

Unsafe

Unsafe.defineClass字节码加载

defineClass方法虽然是public,但是类但不能直接生成

  • 原因是:它的构造函数是private,这个设计是单例模式,Runtime类也是如此,这块儿就,通过反射获得属性theUnsafe获得一个Unsafe对象,来调用defineClass方法

Unsafe类中属性theUnsafe就是一个Unsafe对象

1
2
3
4
5
6
7
8
ClassLoader cl = ClassLoader.getSystemClassLoader();
Class<Unsafe> c = Unsafe.class;
Field theUnsafeField = c.getDeclaredField("theUnsafe");//获得属性theUnsafe
theUnsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe) theUnsafeField.get(null);//获取属性返回结果强转为Unsafe对象
byte[] code= Files.readAllBytes(Paths.get("D:\\tmp\\classes\\Test95.class"));
Class<?> c2 = unsafe.defineClass("Test95", code, 0, code.length, cl, null);
c2.newInstance();

类的动态加载
https://bxhhf.github.io/2025/04/10/类的动态加载/
作者
bxhhf
发布于
2025年4月10日
许可协议