JVM学习笔记【基础篇:类加载器】

类加载器的分类

⚫ 类加载器(ClassLoader)是Java虚拟机提供给应用程序去实现获取类和接口字节码数据的技术。
类加载器只参与加载过程中的字节码获取并加载到内存这一部分在这里插入图片描述

类加载器的分类

分为两类

  • Java代码中实现的
    • JDK中默认提供或者自定义
      • JDK中默认提供了多种处理不同渠道的类加载器,程序员也可以自己根据需求定制
    • 继承自抽象类ClassLoader
      • 所有Java中实现的类加载器都需要继承ClassLoader这个抽象类
  • Java虚拟机底层源码实现的
    • 虚拟机底层实现
      • 源代码位于Java虚拟机的源码中,实现语言与虚拟机底层语言一致,比如Hotspot使用C++
    • 负责加载程序运行时的基础类
      • 保证Java程序运行中基础类被正确地加载,比如java.lang.String,确保其可靠性

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

  • 启动类加载器Bootstrap
    • 加载Java中最核心的类
    • 由Java虚拟机底层源码实现
  • 扩展类加载器Extension
    • 允许扩展Java中比较通用的类
    • Java代码中实现
  • 应用程序类加载器Application
    • 加载应用使用的类
    • Java代码中实现

JVM虚拟机中实现------启动类类加载器

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

  • 在这里插入图片描述

通过启动类加载器去加载用户jar包

  • 放入jre/lib下进行扩展
    • 不推荐,尽可能不要去更改JDK安装目录中的内容,会出现即时放进去由于文件名不匹配的问题也不会正常地被加载。
    • 推荐使用-Xbootclasspath/a:jar包目录/jar包名 进行扩展

Java中的默认类加载器

⚫ ·扩展类加载器和应用程序类加载器都是JDK中提供的、使用Java编写的类加载器。
它们的源码都位于sun.misc.Launcher中,是一个静态内部类。继承自URLClassLoader。具备通过目录或者指定jar包将字节码文件加载到内存中
在这里插入图片描述

扩展类加载器

⚫ 扩展类加载器(Extension Class Loader)是JDK中提供的、使用Java编写的类加载器。
默认加载Java安装目录/jre/lib/ext下的类文件
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/b3ae3c2b7307448aad3218d1f2819ffe.png
通过扩展类加载器去加载用户jar包:

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

应用程序类加载器

⚫ 应用程序类加载器(Application Class Loader)是JDK中提供的、使用Java编写的类加载器。
主要是加载classpath下的类文件
加载用户创建的类以及一些第三方jar中的类

双亲委派机制

在Java中如何使用代码的方式去主动加载一个类呢?

  • 方式1:使用Class.forName方法,使用当前类的类加载器去加载指定的类。
  • 方式2:获取到类加载器,通过类加载器的loadClass方法指定某个类加载器加载。
    • 在这里插入图片描述

每个Java实现的类加载器中保存了一个成员变量叫“父”(Parent)类加载器,可以理解为它的上级,并不是继承关系

  • 应用程序类加载器的parent父类加载器是扩展类加载器,而扩展类加载器的parent是空。
  • 启动类加载器使用C++编写,没有上级类加载器。

在类加载的过程中,每个类加载器都会先检查是否已经加载了该类,如果已经加载则直接返回,否则会
将加载请求委派给父类加载器

如果类加载的parent为null,则会提交给启动类加载器处理
在这里插入图片描述
如果所有的父类加载器都无法加载该类,则由当前类加载器自己尝试加载。所以看上去是自顶向下尝试
加载。

在这里插入图片描述
第二次再去加载相同的类,仍然会向上进行委派,如果某个类加载器加载过就会直接返回

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

在这里插入图片描述
⚫三个问题

  • 重复的类:如果一个类重复出现在三个类加载器的加载位置,应该由谁来加载?
    • 启动类加载器加载,根据双亲委派机制,它的优先级是最高的
  • String类能覆盖吗:在自己的项目中去创建一个java.lang.String类,会被加载吗?
    • 不能,会交由启动类加载器加载在rt.jar包中的String类
  • 类加载器的关系:这几个类加载器彼此之间存在关系吗?
    • 应用类加载器的父类加载器是扩展类加载器,扩展类加载器没有父类加载器,但是会委派给启动类加载器加载。

⚫双亲委派机制有什么用?

  • 保证类加载的安全性
    • 通过双亲委派机制,让顶层的类加载器去加载核心类避免恶意代码替换JDK中的核心类库,比如java.lang.String,确保核心类库的完整性和安全性。
  • 避免重复加载
    • 上层的类加载器如果加载过一次后, 就会直接返回该类,能够避免重复加载类。

打破双亲委派机制

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

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

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

  • 在这里插入图片描述

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

  • 在这里插入图片描述

自定义类加载器

⚫ 先来分析ClassLoader的原理,ClassLoader中包含了4个核心方法
在这里插入图片描述
⚫ 双亲委派机制的核心代码就位于loadClass方法中。
打破双亲委派机制的核心就是修改loadClass方法中的逻辑,使自定义的类加载器不再向上查找是否加载过该类,而是直接原地加载该类
自定义类加载器父类怎么是AppClassLoader呢?

在这里插入图片描述

  • 以Jdk8为例,ClassLoader类中提供了构造方法设置parent的内容
  • 这个构造方法由另外一个构造方法调用,其中父类加载器由getSystemClassLoader方法设置,该方法返回的
    是AppClassLoader。

两个自定义类加载器加载相同限定名的类,不会冲突吗?

  • 不会冲突,在同一个Java虚拟机中,只有相同类加载器+相同的类限定名才会被认为是同一个类
  • 在Arthas中使用sc –d 类名的方式查看具体的情况。

正确的去实现一个自定义类加载器的方式是重写findClass方法,这样不会破坏双亲委派机制

打破双亲委派机制的第二种方法:JDBC案例

⚫ 1、启动类加载器加载DriverManager。
⚫ 2、在初始化DriverManager时,通过SPI机制加载jar包中的myql驱动。
⚫ 3、SPI中利用了线程上下文类加载器(应用程序类加载器)去加载类并创建对象。
⚫ 这种由启动类加载器加载的类,委派应用程序类加载器去加载类的方式,打破了双亲委派机制。
在这里插入图片描述
JDBC案例中真的打破了双亲委派机制吗?

  • 打破了双亲委派机制
    • 这种由启动类加载器加载的类,委派应用程序类加载器去加载类的方式,打破了双亲委派机制
  • 没有打破双亲委派机制
    • JDBC只是在DriverManager加载完之后,通过初始化阶段触发了驱动类的加载,类的加载依然遵循双亲委派机制

打破双亲委派机制的第三种方法: OSGi模块化

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

JDK9之后的类加载器

JDK8及之前的版本中,扩展类加载器和应用程序类加载器的源码位于rt.jar包中的sun.misc.Launcher.java
在这里插入图片描述
⚫ 由于JDK9引入了module的概念,类加载器在设计上发生了很多变化。
1.启动类加载器使用Java编写位于jdk.internal.loader.ClassLoaders类中。Java中的BootClassLoader继承自BuiltinClassLoader实现从模块中找到要加载的字节码资源文件

  • 启动类加载器依然无法通过java代码获取到,返回的仍然是null,保持了统一。

2、扩展类加载器被替换成了平台类加载器(Platform Class Loader)。平台类加载器遵循模块化方式加载字节码文件,所以继承关系从URLClassLoader变成了BuiltinClassLoader,BuiltinClassLoader实现了从模块中加载字节码文件

  • 平台类加载器的存在更多的是为了与老版本的设计方案兼容,自身没有特殊的逻辑

总结

1、类加载器的作用是什么?
类加载器(ClassLoader)负责在类加载过程中的字节码获取加载到内存这一部分。通过加载字节码数据放入内存转换成byte[],接下来调用虚拟机底层方法将byte[]转换成方法区和堆中的数据
2、有几种类加载器?

  • 启动类加载器(Bootstrap ClassLoader)加载核心类。
  • 扩展类加载器(Extension ClassLoader)加载扩展类。
  • 应用程序类加载器(Application ClassLoader)加载应用classpath中的类。
  • 自定义类加载器,重写findClass方法
  • JDK9及之后扩展类加载器(Extension ClassLoader)变成了平台类加载器(Platform ClassLoader)

3、什么是双亲委派机制?
每个Java实现的类加载器中保存了一个成员变量叫“父”(Parent)类加载器自底向上查找是否加载过,再由顶向下进行加载。避免了核心类被应用程序重写并覆盖的问题,提升了安全性。
在这里插入图片描述

4、怎么打破双亲委派机制?

  • 重写loadClass方法,不再实现双亲委派机制。
  • JNDI、JDBC、JCE、JAXB和JBI等框架使用了SPI机制+线程上下文类加载器
  • OSGi实现了一整套类加载机制,允许同级类加载器之间互相调用
  • 20
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值