类的加载器

什么是类加载器

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

类加载器应用场景

企业级应用

  • SPI机制
  • 类的热部署
  • Tomcat类的隔离

面试

  • 什么是类的双亲委派机制
  • 打破类的双亲委派机制
  • 自定义类加载器

线上解决问题

  • 使用Arthas不停机
  • 解决线上故障

类加载器的分类

类加载器分为两类,一类是Java代码中实现的,一类是Java虚拟机底层源码实现的。

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

类加载器的详细信息可以通过classloader命令查看

启动类加载器

  • 启动类加载器(Bootstrap ClassLoader)是由Hotspot虚拟机提供的、使用C++编写的类加载器。
  • 默认加载Java安装目录/jre/lib下的类文件,比如rt.jar(JDK8中最核心的jar包String类,装箱类型Integer、lang,日期类等),tools.jar,resources.jar等。

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

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

扩展类加载器和应用程序类加载器

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

扩展类加载器

  • 扩展类加载器是JDK中提供的、使用Java编写的类加载器。
  • 默认加载Java安装目录/jre/lib/ext下的类文件。

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

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

路径中有空格等特殊字符用双引号引起来。

应用程序类加载器

加载classpath下的类文件

类加载器的加载路径可以通过classloader -c hash值  用Arthas查看:

类加载器的双亲委派机制

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

双亲委派机制有什么用?

1、保证类加载的安全性。

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

2、避免重复加载

双亲委派机制可以避免同一个类被多次加载。

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

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

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

向上查找如果已经加载过,就直接返回Class对象,加载过程结束。这样就能避免一个类重复加载。

向下委派加载起到了一个加载优先级的作用。

双亲委派机制-问题

1、重复的类

如果一个类重复出现在三个类加载器的家在位置,应该由谁来加载?

启动类加载器加载,根据双亲委派机制,它的优先级是最高的。

2、String类能覆盖吗?

在自己的项目中去创建一个java.lang.String类,会被加载吗?

不能,会返回启动类加载器加载在rt.jar包中的String类。

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

1、使用Class.forName方法,使用当前类的类加载器去加载指定的类。

2、获取到类加载器,通过类加载器的loadClass方法指定某个类加载器加载。

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

应用程序类加载器的parent父类加载器是扩展类加载器,而扩展类加载器的parent失控,但是在代码逻辑上,扩展类加载器依然会把启动类加载器当成父类加载器处理。

启动类加载器使用C++编写,没有父类加载器。

Arthas中类加载器相关的功能

类加载器的父子关系可以通过classloader -t 查看:

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

1、当一个类加载器去加载某个类的时候,会自底向上查找是否加载过,如果加载过就直接返回,如果一直到最顶层的类加载器都没有加载,再由顶向下进行加载。

2、应用程序类加载器的父类加载器是扩展类加载器,扩展类加载器的父类加载器是启动类加载器。

3、双亲委派机制的好处有两点:第一是避免恶意代码替换JDK中的核心类库,如java.lang.String,确保核心类库的完整性和安全性。第二是避免一个类重复地被加载。

打破双亲委派机制

1、自定义类加载器

自定义类加载器并重写loadClass方法,就可以将双亲委派机制的代码去除。

2、线程上下文类加载器

利用上下文类加载器加载类,如JDBC和JNDI等。

3、Osgi框架的类加载器

历史上Osgi框架实现了一套新的类加载器机制,允许同级之间委托进行类的加载。

自定义类加载器

一个Tomcat程序中是可以运行多个Web应用的,如果这两个应用中出现了相同限定名的类,如Servlet类,Tomcat要保证这两个类都能加载并且他们应该是不同的类。

如果不打破双亲委派机制,当应用类加载器加载Web应用1中的MyServlet之后,Web应用2中相同限定名的MyServlet类就无法被加载了。

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

  • ClassLoader的原理,ClassLoader中包含了4个和新方法。
  • 双亲委派机制的核心代码就位于loadClass方法中。

自定义类加载器父类怎么是AppClassLoader?

自定义类加载器如果不手动指定parent默认指定应用程序类加载器

自定义类加载器默认的父类加载器

以JDK8为例,ClassLoader类中提供了构造方法设置parent的内容。

这个构造方法由另一个构造方法调用,其中父类加载器由getSystemClassLoader方法设置,该方法返回的是AppClassLoader。

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

不会冲突,在同一个Java虚拟机中,只有相同类加载器+相同的类限定名才会被人为是同一个类。

在Arthas中使用sc -d 类名的方式查看具体的情况。(sc是查看类的,-d就是查看详细信息)

线程上下文类加载器

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

DriverManager类位于rt.jar包中,由启动类加载器加载。

依赖中的mysql驱动对应的类,由应用程序类加载器来加载。

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

启动类加载器加载完DriverManager后,要委派给最下方的应用程序类加载器去加载jar包中的mysql驱动。

DriverManager怎么知道jar包中要加载的驱动在哪儿?

spi机制

  • spi全称为(Service Provider Interface),是JDK内置的一种服务提供发现机制。
  • spi的工作原理:

1、在ClassPath路径下的META-INF/services文件夹中,以接口的全限定名来命名文件名,对应的文件里面写该接口的实现。(暴露要加载的类)

2、使用ServiceLoader加载实现类。

将接口的名字+.class传进来,就给出一个加载器,这个加载器就可以使用迭代器拿到当前的类名并创建对象

SPI中是如何获取到应用程序类加载器的?

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

总结:

1、启动类加载器加载DriverManager。

2、在初始化DriverManager时,通过SPI机制加载jar包中的mysql驱动。

3、SPI中利用了线程上下文类加载器(应用程序类加载器)去加载类并创建对象。

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

OSGi模块化

历史上,OSGi模块化框架。它存在同级之间的类加载器的委托加载。OSGi还使用类加载器实现了热部署的功能。

热部署指的是在服务不停止的情况下,动态地更新字节码文件到内存中。

JDK9之后的类加载器

JDK8及之前的类加载器

JDK及之前的版本中,扩展类加载器和应用程序类加载器的源码位于rt.jar包中的sun.misc.Launcher.java。

由于JDK9引入了module的概念,类加载器在设计上发生了很多变化。

1、启动类加载器使用Java编写,位于jdk.internalloader.ClassLoaders类中。

Java中的BootClassLoader继承自BuiltinClassLoader实现从模块中找到要加载的字节码资源文件。

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

2、扩展类加载器被替换成了平台类加载器。

平台类加载器遵循模块化方式加载字节码文件,所以继承关系从URLClassLoader变成了BuiltinClassLoader,BuiltinClassLoader实现了从模块中加载字节码文件。平台类加载器的存在更多的是为了与老版本的设计方案兼容,自身没有特殊的逻辑。

  • 9
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值