类的动态加载
类的动态加载
回顾
静态代码块在类加载时已经被加载了
- 静态代码块用来初始化静态成员变量的,在类加载的初始化阶段执行
构造代码块和构造函数都是在实例化对象时会被调用,构造代码块依赖于构造函数,先于构造函数执行
- 构造代码块的作用也是和构造函数的一样,用于初始化对象,并且只要创建一个对象,构造代码块都会执行一次,但创建一个对象会自动选择调有参还是无参构造方法,构造方法不会全调
初始化:类的class不会触发初始化,Class的forName和Object的getClass会触发初始化
**实例化会触发…**:1.类加载初始化 2.构造代码块 3.(有参或无参)构造器 {就是静态代码块和构类的}
1 | |

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

ClassLoader :抽象类
它里面的静态方法getSystemClassLoader() 获得系统加载类
当传参布尔值为false时,由Class.forName()创建类对象时不会触发初始化!!
1 | |
ClassLoader.loadClass()
它 不触发初始化,类加载器获取到Class类对象,并实例化
欸这不就是之前学类加载过程时那句话的具体实现嘛:程序编译成class文件后,通过类加载器加载Class对象到堆中。每一个加载到内存的类都由一个 Class 对象来表示每一个 Class 对象都有一个指向加载该类的类加载器的引用,通过 getClassLoader()方法就可以获取到此引用
1 | |
父类加载器
先了解一下父类加载器吧
对于开发人员编写的类加载器来说
- 其父类加载器是加载此类加载器 Java 类的类加载器。因为类加载器 Java 类如同其它的 Java 类一样,也是要由类加载器来加载的。调用
getParent()方法来输出父类加载器。一般来说,开发人员编写的类加载器的父类加载器是系统类加载器(AppClassLoader)。
- 其父类加载器是加载此类加载器 Java 类的类加载器。因为类加载器 Java 类如同其它的 Java 类一样,也是要由类加载器来加载的。调用
对于系统提供的类加载器来说
- 系统类加载器(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

Launcher是JRE中用于启动程序入口main()的类,让我们看下Launcher的代码
1 | |
在虚拟机启动的时候会初始化BootstrapClassLoader,然后在Launcher类中去加载ExtClassLoader、AppClassLoader,并将AppClassLoader的parent设置为ExtClassLoader,并设置线程上下文类加载器。
类的加载工作
这块儿具体就是一个双亲委派机制~~
- 检查该类是否已经被当前的类加载器加载。因为一旦一个类被加载到JVM中,同一个类就不会被再次载入了。加载到内存中的Class对象是唯一的

若已经加载过则直接返回对应的Class实例就好了
若没加载,则就要委托给父加载器,现在是APP,则让父类加载器EXT加载(调用父类加载器的loadClass,还是这个代码)
Ext没有(parent=null,实际上是等于Bootstrap ClassLoader,因为它是c写的所以在java中就是null),则看是已经被启动类加载器BootstrapClassLoader加载
(下面的代码在AppClassLoader时走的是if后面的,ExtClassLoader时走的是else后面的)

!!!!!!现在还在Ext还没出去,上面的都没加载,则尝试该类加载器Ext自己加载,则调用
findClass()方法加载类
调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),但看服务是收到了请求的

在快放下它的时候终于终于在一篇文章里知道原因了!!~~,因为编译的时候没有删除package全包名,(带上包名,URLClassLoader当然会从本地去加载指定class类了那肯定就找不到我们外部加载的类)
下面是它的加载代码
http协议:
1 | |
本地加载;
1 | |
jar:
生成jar文件
先编译.java文件生成.class文件
在终端对应目录执行命令 jar cf myapp.jar MyClass.class(
c表示创建一个新的 JAR 文件。f指定 JAR 文件的名称,这里是myapp.jar。MyClass.class是要包含在 JAR 文件中的编译后的 Java 类文件。)
1 | |
jar包也可以用http协议
1 | |
defineClass()
ClassLoader.defineClass 字节码加载任意类,defineClass是一个protected的方法,所以就得反射调用
1 | |
Unsafe
Unsafe.defineClass字节码加载
defineClass方法虽然是public,但是类但不能直接生成
- 原因是:它的构造函数是private,这个设计是单例模式,Runtime类也是如此,这块儿就,通过反射获得属性theUnsafe获得一个Unsafe对象,来调用defineClass方法
Unsafe类中属性theUnsafe就是一个Unsafe对象
1 | |