当前位置:   article > 正文

【JVM】类加载器_应用程序类加载器

应用程序类加载器

【JVM】类加载器

0. 类加载器概述

类加载器(ClassLoader)是Java虚拟机提供给应用程序去实现获取类和接口字节码数据的技术。
image-20231025141442943
image-20231025141442943


1. 类加载器的分类

类加载器分为两类:

  1. Java代码中实现的类加载器
  2. JVM底层源码实现的类加载器

image-20231025141442943

jdk8和8之后版本的类加载器的设计差别较大,jdk8及之前的版本中默认的类加载器有如下几种:

  1. JVM底层实现(C++):
    • 启动类加载器Bootstrap:加载Java中最核心的类
  2. Java:
    • 扩展类加载器Extension:允许扩展Java中比较通用的类
    • 应用程序类加载器Application:加载应用使用的类

1.1 启动类加载器

**启动类加载器(Bootstrap ClassLoader)**是由 Hotspot 虚拟机提供的,使用C++编写的类加载器。默认加载Java安装目录/jre/lib下的类文件,比如 rt.jar,tools.jar,resources.jar等。

使用启动类加载器去加载用户jar包有两种方式:

  1. 将jar包放入 jre/lib 目录下进行扩展
    • 不推荐,尽可能不要去更改JDK安装目录中的内容,因为就算将jar包放入该目录下也可能由于文件名不匹配的问题导致jar包不会正常的被加载。
  2. 使用参数进行扩展
    • 推荐,使用 -Xbootclasspath/a:路径/jar包名.jar 进行扩展。

1.2 Java中的默认类加载器

扩展类加载器应用程序类加载器都说JDK中提供的、使用Java编写的类加载器。它们的源码都位于 sun.misc.Launcher 中,是一个静态内部类。继承自 URLClassLoader可以通过目录或者指定jar包将字节码文件加载到内存中

image-20231025164359439


1.2.1 扩展类加载器

**扩展类加载器(Extension ClassLoader)**是jdk中提供的、使用Java编写的类加载器。默认加载Java安装目录 /jre/lib/ext 下的类文件。

通过扩展类加载器去加载用户jar包的方式:

  1. 放入 jre/lib/ext 下进行扩展。
    • 不推荐,尽可能不要去更改jdk安装目录中的内容。
  2. 使用参数进行扩展
    • 推荐,使用 -Djava.ext.dirs=jar包目录 进行扩展,这种方式会覆盖掉原始目录,随意我们应该用 ;(windows)或 :(macos/linux)追加上原始目录。

1.2.2 应用程序类加载器

应用程序类加载器(AppClassLoader):面向我们用户的加载器,负责加载当前应用 classpath 下的所有 jar 包和类。


2. 双亲委派机制

由于JVM中有多个类加载器,双亲委派机制的核心是解决一个类到底由谁加载的问题。

双亲委派机制的作用:

  1. 保证类加载的安全性:通过双亲委派机制避免恶意代码替换jdk中的核心类库,比如 java.lang.String ,确保核心类库的完整性和安全性。
  2. 避免重复加载:双亲委派机制可以避免同一个类被多次加载。

双亲委派机制指的是:当一个类加载器接收到加载类的任务时,会自底向上查找是否加载过,再由顶向下进行加载
image-20231025141442943

  1. 向上查找:
    • 向上查找如果已经加载过,就直接返回Class对象,加载过程结束。这样就能避免一个类重复加载
  2. 向下加载:
    • 如果所有父类加载器都无法加载该类,则由当前类加载器自己尝试加载。所以看上去是自顶向下尝试。
    • 第二次再去加载相同的类,仍会向上进行委派,如果某个类加载器加载过就会直接返回。

每个Java实现的类加载器中都保存了一个成员变量名为 parent 的类加载器,**可以理解为它的上级,并不是继承关系。**应用程序类加载器的parent父类加载器是扩展类加载器,而扩展类加载器的parent是空,因为启动类加载器由C++实现,无法在Java中获得。


2.1 类的双亲委派机制是什么?

类的双亲委派机制是什么?

  1. 当一个类加载器去加载某个类的时候,会自底向上查找是否加载过,如果加载过就直接返回Class对象,如果一直到最顶层的类加载器都没有加载,再自顶向下进行加载。
  2. 应用程序类加载器的父类加载器是扩展类加载器,扩展类加载器的父类加载器是启动类加载器。
  3. 双亲委派机制的好处:
    • 避免恶意代码替换jdk中核心类库,确保核心类库的完整性和安全性。
    • 避免类被重复加载。

2.2 打破双亲委派机制

打破双亲委派机制的三种方式:

  1. 自定义类加载器:自定义类加载器并且重写 loadClass 方法,就可以将双亲委派机制的代码去除。
  2. 线程上下文类加载器:利用上下文类加载器加载类,比如JDBC和JNDI等。
  3. Osgi框架的类加载器:历史上Osgi框架实现了一套新的类加载器机制,允许同级之间委托进行类的加载。

2.2.1 自定义类加载器
  • 一个Tomcat程序中可以运行多个Web应用,如果这两个应用中出现了相同限定名的类,比如Servlet类,Tomcat要保证这两个类都能加载并且它们应该是不同的类。
  • 如果不打破双亲委派机制,当应用类加载器加载Web应用1中的MyServlet之后,Web应用2中相同限定名的MyServlet类就无法被加载了。
    image-20231025141442943

Tomcat使用了自定义类加载器来实现应用之间类的隔离。每一个应用会有一个独立的类加载器加在对应的类。
image-20231025141442943

ClassLoader中包含了4个核心方法(双亲委派机制的核心代码就位于loadClass方法中):
image-20231025141442943

打破双亲委派机制的关键就是重写 loadClass 方法中的逻辑。


2.2.2 线程上下文类加载器

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

DriverManager 类位于 rt.jar 包中,由启动类加载器加载。而依赖中的mysql驱动对应的类,由应用程序类加载器来加载。这就违反了双亲委派机制。
image-20231025141442943

DriverManager 使用SPI机制,最终加载jar包中对应的驱动类。
image-20231025141442943

那么SPI机制是如何获取到应用程序类加载器的呢?

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

public static <S> ServiceLoader<S> load(Class<S> service){
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    return ServiceLoader.load(service,cl);
}
  • 1
  • 2
  • 3
  • 4

完整流程:

  1. 启动类加载器加载 DriverManager
  2. 在初始化 DriverManager 时,通过SPI机制加载jar包中的mysql驱动
  3. SPI中利用了线程上下文类加载器(应用程序类加载器)去加载类并创建对象。

思考

JDBC案例真的打破了双亲委派机制吗?

有两种说法:

  1. 打破了双亲委派机制:这种由启动类加载器加载的类,委派应用程序类加载器去加载类的方式,打破了双亲委派机制。
  2. 没有打破双亲委派机制:类加载流程中,没有违反双亲委派机制。因为 DriverManager 位于rt.jar包下,由启动类加载器加载,而mysql驱动位于classpath,由应用程序类加载器加载,没有问题。

2.3 OSGi模块化

历史上,OSGi模块化框架。它存在同级之间的类加载器的委托加载。OSGi还使用类加载器实现了热部署(在服务不停止的情况下,动态更新字节码文件到内存中)的功能。

image-20231025224629529


3. 总结

  1. 类加载器的作用是什么?

答:类加载器(ClassLoader)负责在类加载过程当中获取字节码并加载到内存中转换成byte[],接下来调用虚拟机底层方法将byte[]转换成方法区和堆中的数据。


  1. 有几种常见的类加载器?

答:

  • 启动类加载器:加载核心类
  • 扩展类加载器:加载扩展类
  • 应用程序类加载器:加载应用classpath中的类
  • 自定义类加载器:重写findClass方法

  1. 什么是双亲委派机制

答:每个Java实现的类加载器中都保存了一个成员变量叫 parent 的类加载器。自底向上查找是否加载过,再由顶向下进行加载。避免核心类被应用程序重写并覆盖的问题,提升了安全性。
image-20231025141442943


  1. 怎么打破双亲委派机制?

答:

  1. 重写loadClass方法。
  2. JNDI、JDBC、JCE、JAXB和JBI等框架使用了SPI机制+线程上下文类加载器
  3. OSGi实现了一套类加载机制,允许同级类加载器之间互相调用。
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/黑客灵魂/article/detail/845256
推荐阅读
相关标签
  

闽ICP备14008679号