首页 /  技术专区  /  JVM 宽屏模式 >

类加载器

什么是类加载器

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

类加载器只参与加载过程中的字节码获取并加载到内存这一部分


类加载器的分类

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

Image.png

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

Image.png

Arthas中类加载器相关的功能

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

classloader - 查看 classloader 的继承树,urls,类加载信息,使用 classloader 去 getResource

Image.png

类加载器的分类-启动类加载器

启动类加载器(Bootstrap ClassLoader)是由Hotspot虚拟机提供的、使用C++编写的类加载器。

默认加载Java安装目录/jre/lib下的类文件,比如rt.jar,tools.jar,resources.jar等。

Image.png


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

放入jre/lib下进行扩展

不推荐,尽可能不要去更改JDK安装目录中的内容,会出现即时放进去由于文件名不匹配的问题也不会正常地被加载

使用参数进行扩展

推荐,使用-Xbootclasspath/a:jar包目录/jar包名 进行扩


类加载器的分类-Java中的默认类加载器

扩展类加载器和应用程序类加载器都是JDK中提供的、使用Java编写的类加载器。

它们的源码都位于sun.misc.Launcher中,是一个静态内部类。继承自URLClassLoader。具备通过目录或者指定jar包将字节码文件加载到内存中。

Image.png

类加载器的分类-拓展类加载器

扩展类加载器(Extension Class Loader)是JDK中提供的、使用Java编写的类加载器。

默认加载Java安装目录/jre/lib/ext下的类文件。

Image.png

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

放入/jre/lib/ext下进行扩展

不推荐,尽可能不要去更改JDK安装目录中的内容

使用参数进行扩展

推荐,使用-Djava.ext.dirs=jar包目录 进行扩展,这种方式会覆盖掉原始目录,可以用;(windows):(macos/linux)追加上原始目录


Arthas中类加载器相关的功能

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

Image.png


类加载器的双亲委派机制

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

Image.png

应用程序类加载器的parent父类加载器是扩展类加载器,而扩展类加载器的parent是空。

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

Image.png

Arthas中类加载器相关的功能

类加载器的继承关系可以通过classloader –t 查看:

Image.png

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

Image.png

如果类加载的parentnull,则会提交给启动类加载器处理。

Image.png

如果所有的父类加载器都无法加载该类,则由当前类加载器自己尝试加载。所以看上去是自顶向下尝试加载。

Image.png

第二次再去加载相同的类,仍然会向上进行委派,如果某个类加载器加载过就会直接返回。

Image.png

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

Image.png

双亲委派机制-解决三个问题

Image.png

双亲委派机制的作用

1.保证类加载的安全性

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

2.避免重复加载

双亲委派机制可以避免同一个类被多次加载,上层的类加载器如果加载过类,就会直接返回该类,避免重复加载。


打破双亲委派机制

Image.png

打破双亲委派机制自定义类加载器

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

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

Image.png

Tomcat使用了自定义类加载器来实现应用之间类的隔离。

每一个应用会有一个独立的类加载器加载对应的类。

Image.png

先来分析ClassLoader的原理,ClassLoader中包含了4个核心方法。

双亲委派机制的核心代码就位于loadClass方法中。

Image.png

双亲委派机制核心代码阅读

阅读双亲委派机制的核心代码,分析如何通过自定义的类加载器打破双亲委派机制。

打破双亲委派机制的核心就是将下边这一段代码重新实现。

Image.png

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

Image.png

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

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

Image.png


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

Image.png

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

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

在Arthas中使用sc –d 类名的方式查看具体的情况。


打破双亲委派机制 – 自定义类加载器

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

Image.png

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

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

Image.png

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

Image.png

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

Image.png

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

Image.png

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

Image.png

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

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

Image.png

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

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

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

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

Image.png

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

打破了双亲委派机制

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


没有打破双亲委派机制

JDBC只是在DriverManager加载完之后,通过初始化阶段触发了驱动类的加载,类的加载依然遵循双亲委派机制


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

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

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

Image.png

使用阿里arthas不停机解决线上问题

背景:

小李的团队将代码上线之后,发现存在一个小bug,但是用户急着使用,如果重新打包再发布需要一个多小时的时间,所以希望能使用arthas尽快的将这个问题修复。

思路:

1. 在出问题的服务器上部署一个 arthas,并启动。

2. jad --source-only 类全限定名 > 目录/文件名.java

    jad 命令反编译,然后可以用其它编译器,比如 vim 来修改源码

3. mc –c 类加载器的hashcode 目录/文件名.java -d 输出目录

    mc 命令用来编译修改过的代码

4. retransform class文件所在目录/xxx.class

    用 retransform 命令加载新的字节码

注意事项:

1、程序重启之后,字节码文件会恢复,除非将class文件放入jar包中进行更新。

2、使用retransform不能添加方法或者字段,也不能更新正在执行中的方法。


JDK8及之前的类加载器

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

Image.png

JDK8之后的类加载器

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

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

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

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

Image.png

2、扩展类加载器被替换成了平台类加载器(Platform Class Loader)。

平台类加载器遵循模块化方式加载字节码文件,所以继承关系从URLClassLoader变成了

BuiltinClassLoader,BuiltinClassLoader实现了从模块中加载字节码文件。平台类加载器的存在更多的是

为了与老版本的设计方案兼容,自身没有特殊的逻辑。

Image.png


总结

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

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


2.有几种类加载器?

1.启动类加载器(Bootstrap ClassLoader)加载核心类

2.扩展类加载器(Extension ClassLoader)加载扩展类

3.应用程序类加载器(Application ClassLoader)加载应用classpath中的类

4.自定义类加载器,重写findClass方法。

JDK9及之后扩展类加载器(Extension ClassLoader)变成了平台类加载器(Platform ClassLoader)

Image.png

3、什么是双亲委派机制?

每个Java实现的类加载器中保存了一个成员变量叫“父”(Parent)类加载器。

自底向上查找是否加载过,再由顶向下进行加载。避免了核心类被应用程序重写并覆盖的问题,提升了安全性。

Image.png


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

1、重写loadClass方法,不再实现双亲委派机制。

2、JNDI、JDBC、JCE、JAXB和JBI等框架使用了SPI机制+线程上下文类加载器。

3、OSGi实现了一整套类加载机制,允许同级类加载器之间互相调用。







头像
0/200
图片验证码