Java中类加载器举例详解
类加载器简介
Java程序被编译器编译之后成为字节码文件(.class文件),当程序需要某个类时,虚拟机便会将对应的class文件进行加载,创建出对应的Class对象。而这个将class文件加载到虚拟机内存的过程,便是类加载。

类加载器负责在运行时将Java类动态加载到JVM(Java虚拟机),是JRE(Java运行时环境)的一部分。由于类加载器的存在,JVM无需了解底层文件或文件系统即可运行Java程序。并且Java类不会一次全部加载到内存中,而是在需要时才会加载到内存中。
类加载的过程
类的生命周期通常包括:加载、链接、初始化、使用和卸载。上图中包含了类加载的三个阶段:加载阶段、链接阶段和初始化阶段。如果将这三个阶段再拆分细化包括:加载、验证、准备、解析和初始化。

这几个阶段的作用简单概况一下:
- 加载:通过一个类的完全限定查找类字节码文件,转化为方法区运行时的数据结构,创建一个代表该类的Class对象。
- 验证:确保Class文件的字节流中包含信息符合当前虚拟机要求,不会危害虚拟机自身安全。
- 准备:为类变量(即static修饰的字段变量)分配内存并且设置该类变量的初始值。不包含被final修饰的static变量,因为它在编译时已经分配了。
- 解析:将常量池内的符号引用转换为直接引用的过程,比如(main()方法)替换为指向数据所存内存的指针或句柄等(直接引用),这是所谓的静态链接过程(类加载期间完成),动态链接是在程序运行期间完成的将符号引用替换为直接引用。如果符号引用指向一个未被加载的类,或者未被加载类的字段或方法,那么解析将触发这个类的加载。
- 初始化:类加载最后阶段,执行静态初始化器和静态初始化成员变量。对类的静态变量初始化为指定的值,执行静态代码块。
在上述类加载的过程中,虚拟机提供了三种类加载器:启动(Bootstrap)类加载器、扩展(Extension)类加载器、系统(System)类加载器(也称应用类加载器),此外还可以自定义类加载器。


启动(Bootstrap)类加载器:
系统中最顶层、最核心的加载器,是 JVM 与生俱来的一部分,负责加载 JDK 核心类库,奠定 Java 程序运行的基础,由 JVM 的底层代码(C/C++,如 HotSpot 虚拟机)实现,无法在 Java 代码中直接获取其对象(调用 getClassLoader() 返回 null)。
加载范围:加载 JDK 核心类库
- JDK 8 及之前:加载
<JAVA_HOME>/jre/lib目录下的核心 JAR 包(如rt.jar、charsets.jar、sunrsasign.jar等),这些 JAR 是 JVM 识别的「核心类库」(非该目录下的任意 JAR 不会被加载); - JDK 9+(模块化):移除了
rt.jar等打包方式,核心类以「模块」形式存储在<JAVA_HOME>/lib/modules(模块化镜像文件),启动类加载器负责加载java.base等核心模块(如java.lang、java.io所属的模块)。
扩展(Extension)类加载器:
扩展类加载器是启动类加载器的子类,Java语言编写,由sun.misc.Launcher$ExtClassLoader实现,父类加载器为启动类加载器,负责加载标准核心Java类的扩展。
扩展类加载器从JDK扩展目录(通常是$JAVA_HOME/lib/ext目录)或java.ext.dirs系统属性中指定的任何其他目录进行自动加载。
系统/应用(System)类加载器:
系统类加载器负责将所有应用程序级类加载到JVM中。它加载在类路径环境变量,-classpath或-cp命令行选项中找到的文件。它是扩展类加载器的子类。
自定义类加载器:
在大多数情况下,内置的三种类加载器就足够了。但是,在需要从本地硬盘驱动器或网络中加载类的情况下,需要打破双亲委派机制,则可能需要使用自定义类加载器。
下面介绍类加载器的加载机制--双亲委派机制。
双亲委派机制:

从图中可以清晰的看到双亲委派机制的具体过程:加载某个类时会先层层委托给父加载器寻找目标类加载,如果父加载器在自己的加载类路径下都找不到目标类,尝试向下加载。比如我们自己随便写的User类,最先会找应用程序类加载器加载,应用程序类加载器会先委托扩展类加载器加载,扩展类加载器再委托启动类加载器,顶层启动类加载器在自己的类加载路径里找了半天没找到User类,则向下退回加载User类的请求,扩展类加载器收到回复就自己加载,在自己的类加载路径里找了半天也没找到User类,又向下退回User类的加载请求给应用程序类加载器,应用程序类加载器于是在自己的类加载路径里找User类,结果找到了就自己加载了。双亲委派机制说简单点就是,先找父亲加载,不行再由儿子自己加载。
我们来看一下loadclass的源码:
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
PerfCounter.getParentDelegationTime().addTime(t1 - t0);
PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}此方法负责加载给定名称参数的类。name参数为类的全限定名。先查找是否已加载该类,若无,则判断有无父类加载器parent,尝试给父类加载器去loadclass,其实就是一个递归的顺序。
为什么要双亲委派机制:
沙箱安全机制:自己写的java.lang.String.class类不会被加载,这样便可以防止
核心API库被随意篡改( 可以自己写一个String 类 会发现报错 )
避免类的重复加载:当父亲已经加载了该类时,就没有必要子ClassLoader再加
载一次,保证被加载类的唯一性
打破双亲委派机制示例:
1、tomcat:tomcat就是属于一个web 程序,可能部署俩个应用程序,不同的应用程序可能会依赖一个第三方类库的不同版本,不能要求同一个类库在同一个服务器只有一份,因此要保证每个应用程序的类库都是独立的,保证相互隔离,就需要打破双亲委派机制。
2、热部署:热部署要求 “修改类 / JSP 后,无需重启应用即可生效”,而双亲委派下,类加载器加载类后,该类会被 JVM 缓存,类加载器不销毁,类就无法重新加载。因此需要自定义类加载器 + 销毁重建。
总结
到此这篇关于Java中类加载器的文章就介绍到这了,更多相关Java类加载器内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
SpringBoot AOP @Pointcut切入点表达式排除某些类方式
这篇文章主要介绍了SpringBoot AOP @Pointcut切入点表达式排除某些类方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教2021-11-11


最新评论