JVM 类加载器 详解

发布于:2025-06-09 ⋅ 阅读:(17) ⋅ 点赞:(0)

类加载器

两个类来源于同一个 Class文件,被同一个Java虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等

这里所指的“相等”,包括代表类的Class对象的equals()方法、isAssignableFrom()方法、isInstance()方法的返回结果,也包括了使用instanceof关键字做对象所属关系判定等各种情况

总结:

类加载器

​ 类加载器作用于类加载的加载阶段,也就是加载class文件到方法区中,并且在方法区生成Class对象,通过Class对象可以访问方法区中的类信息。每个Class对象都有一个引用指向加载它的类加载器。

​ JVM中内置了三个类加载器,启动类加载器、扩展类加载器、应用程序类加载器。其中,启动类加载器用来加载JAVA_HOME/lib下的核心类库比如rt.jar;扩展类加载器用来加载JAVA_HOME/lib/ext下的类;应用程序类加载器用来加载classpath下的类。

​ 这三个类加载器都继承自ClassLoad抽象类,类加载器可以通过getParent()方法来获取它的父加载器,例如应用程序类加载器的父加载器是扩展类加载器,而扩展类加载器的父加载器是启动类加载器,只不过扩展类加载器通过getParent()方法获得的是null,因为启动类加载器是基于c++实现的。

​ 三个加载器的父子关系不是通过继承来实现的,而是通过组合方式来实现的。

双亲委派模型:

  • 启动类加载器(Bootstrap Class Loader):是由Hotspot虚拟机提供的类加载器,JDK9之前使用C++编写的、JDK9之后使用Java编写。默认加载Java安装目录/jre/lib下的类文件,比如rt.jar,tools.jar,resources.jar等。
  • 扩展类加载器(Extension Class Loader):默认加载Java安装目录/jre/lib/ext下的类文件。(JDK9及以后,被重命名为平台类加载器Platform ClassLoader)
  • 应用程序类加载器(Application Class Loader):负责加载用户类路径(ClassPath)上所有的类库

双亲委派机制指的是:自底向上查找是否加载过,再由顶向下进行加载

​ 双亲委派模型:除了顶层的启动类加载器外,其余的类加载器都应有自己的父类加载器。不过这里类加载器之间的父子关系一般不是以继承(Inheritance)的关系来实现的,而是通常使用组合 (Composition)关系来复用父加载器的代码。

在这里插入图片描述

​ 如果一个类加载器收到了类加载的请求,每个类加载器都会先检查是否已经加载了该类,如果已经加载则直接返回,否则会将加载请求委派给父类加载器。每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时, 子加载器才会尝试自己去完成加载。

为什么使用双亲委派:

​ 1.保证类加载的安全性,通过双亲委派机制避免恶意代码替换JDK中的核心类库,比如java.lang.String,确保核心类库的完整性和安全性。

​ 2.避免重复加载,双亲委派机制可以避免同一个类被多次加载。

为什么要向上委派,不能向下委派?

​ 是否能一开始就由启动类加载器加载所有的类,启动类加载器发现不在自己加载范围内的类就交给自己的子类加载器?答案是不行的,因为一个类加载器可以只有一个父类加载器,需要加载类时直接向上委派就行,但如果是向下委派,同时又有多个子类加载器,这时候就不知道要委派给哪个子类加载器的(可能有多个子类加载器的原因是因为可以有自定义类加载器,加载器的组合是多样的,但不管怎样,只可能有一个父类加载器),这就好比一个二叉树,从叶子节点向上走,是有且只有一条唯一的路径的,而且这条路径的终点必然是根节点,而从根节点出发,一直向下走,是不能确定一条唯一路径的,虽然最终能到达某一叶子节点,但具体是哪个叶子节点取决于每次向下走的路线决策。而且向下委派意味着需要修改应用程序类加载器的源码。
在这里插入图片描述

破坏双亲委派模型:

在这里插入图片描述

自定义加载器的话,需要继承 ClassLoader 。如果我们不想打破双亲委派模型,就重写 ClassLoader 类中的 findClass() 方法即可,无法被父类加载器加载的类最终会通过这个方法被加载。但是,如果想打破双亲委派模型则需要重写 loadClass() 方法。

Tomcat采用自定义类加载器破坏了双亲委派,实现了Web应用之间的类的隔离
在这里插入图片描述
在这里插入图片描述

JDBC:

​ JDBC中使用了DriverManager来管理项目中引入的不同数据库的驱动,比如mysql驱动、oracle驱动。

DriverManager属于rt.jar是启动类加载器加载的。而用户jar包中的驱动需要由应用类加载器加载,这就违反了双亲委派机制。

​ DriverManage使用SPI机制,最终加载jar包中对应的驱动类。

​ SPI中使用了线程上下文中保存的类加载器进行类的加载,这个类加载器一般是应用程序类加载器。

这种由启动类加载器加载的类,委派应用程序类加载器去加载类的方式,打破了双亲委派机制。

​ JDBC接口是jre下的,但实现是第三方供应商提供的,按理来说,一个类及其依赖类由同一个类加载器加载(确保这些类之间的依赖关系正确并保持一致),但这种情况下不会被同一个类加载器加载,这就需要用到线程上下文类加载器解决

​ 线程上下文类加载器:当一个线程启动时,jvm会将应用类加载器赋值给当前线程的线程上下文类加载器,此时父类加载器就可以提高线程上下文类加载器获取到子类加载器,再由子类加载器加载父类找不到的类

在这里插入图片描述

什么时候需要自定义类加载器:

  • 想加载非classpath随意路径的类文件
  • 隔离同名类,需要不同应用下的同名类可以不冲突,如tomcat容器