java中classloader的作用
classLoader的作用
java的类加载机制,从9开始,已经做出了更改,本文基于java9,所以会和其它的资料不太一样
- jvm使用classLoader加载
.class
文件变成class
对象 - 每个
class
对象都有自己的ClassLoader
对象,谁加载的它,它的ClassLoader
就是谁,这个可能是native的defineClass
逻辑中的方法决定的 - jvm使用
ClassLoader
+类名字来确定一个类,所以,不同ClassLoader
的类无论是否一样,都不会强转成功的 - 一个
ClassLoader
只能加载一个类一次,再次加载相同名字的类,native方法会报错(只要你用了defineClass
方法,但是你也必须用)
按需加载
ClassLoader
按照实际的需要加载需要的类,一个类如果被关联,那么它会被ClassLoader
从给定的方式找到,如果找不到,
就是ClassNotFoundException
,对于java9来说,有一种很方便的方法可以看到到底加载了多少类
1 | java <app_name> -Xlog:class+load=info:classloaded.txt |
1 | [0.001s][info][class,load] opened: /home/ss/.jdks/azul-15.0.6/lib/modules |
随便一个helloWord,基本上都需要加载大约900多个类,包含内部类,而不会把所有的类都加载上
分层加载
jvm的规范已经定了,class文件也已经编译好了,加载更多的时候,只是从哪里找到class这个问题,对于java9
bootstrap
类加载器BootClassLoader
,现在变成了java写的,但是为了兼容性,String.class
的ClassLoader
还是nullplatform
类加载器PlatformClassLoader
,PlatformClassLoader
的parent现在不是null了,而是BootClassLoader
app
类加载器AppClassLoader
,父加载器是platform
同样的继承体系,但是加载的时候,自己写的模块肯定是app
加载的,但是jdk的代码,可能是bootstrap
,platform
,app
加载的,当然,String
,System
这些核心类还是bootstrap
加载的,具体的模块可以用以下代码测试出来
1 | public class App { |
1 | String->null |
debug模式下,可以通过nameToModule
看到
优先父类加载(双亲委派)
类加载的时候,优先会让父类加载,然后一级级往上找,正好两层父类,所以,也叫双亲委派
,经常面试中问到,其实就是个优先级问题,这样做有两个需要
- 确保不重复加载类,确保类的唯一性,
- 也顺带解决了一部分安全性,比如自己写个string,肯定就不能加载成功了,当然,jdk肯定也有其它的安全机制来保证的
自定义类加载器
jvm的类规范已经定好了,你的classpath
也已经写好了,那么还为什么需要自定义类加载器呢,主要是一些框架,或者是中间件才需要,比如tomcat,有自己的lib,还有flink
也有别的lib需要加载,这些时候,就需要从别的地方来加载类,还有些需求,比如你的类是加密的,这个时候,肯定也是需要自定义类加载器的
ClassLoader
中,有三个方法我们需要注意loadClass
,findClass
,defineClass
,
loadClass
定义了父类优先加载的机制,如果你需要打破这个机制,如flink
那么你就需要重写这个方法,可以变成子类优先findClass
,如果你只是需要加密解密,那么你的逻辑在这里做,就很合适了,找到对应的文件,读取成字节数组,然后解密以后,调用defineClass
返回即可defineClass
的实现,主要逻辑是本地方法,把字节数组,变成class对象,你基本上无需关心,只需要知道它的作用使用即可
Class.forName对比ClassLoader.loadClass
两个方法基本作用相同,但是forName
是的主要逻辑在本地方法,可以加载基本类型的数组类型,虽然loadClass
虽然也会让父加载器去找,但是找不到,注意这种区别,一般应该优先使用forName
方法去加载类
多版本的类共存
比如框架本身需要一个类,但是下面的代码又可能使用一个不知道什么版本的类,这个时候,既不能指定下面的代码使用什么版本,同时框架自身也要用,shade插件在这个时候,就起到作用了,具体的工作原理就是,把类名全部改了,然后把框架本身引用到的地方也全部都改了,这样,通过改类名,相对比较简单的就做到了多个版本的类共存,对应的maven插件叫maven-shade-plugin
,比如flink
,skywalking
这类中间件都会用到这样的技术
当前线程的类加载器
如果不同的线程,需要不同的ClassLoader
来加载多个版本的Class
,或者加载Class
的机制不一样,那么,可以使用java.lang.Thread#getContextClassLoader
,不同的线程使用不同的ClassLoader
,起到很好的隔离作用
一般的场景用不到,一般用在框架或者中间件的需求中,比如flink
,不同的地方使用的ClassLoader
不一样,通过setContextClassLoader
来隔离,getContextClassLoader
来使用
总结
其实大部分情况下,你不开发框架,不开发中间件,基本上是用不到ClassLoader
的,但是为什么面试中,还是经常会问呢?中国人讲究学以致用,但是如果你用不到这样的场景呢?你还用学嘛?所以了解一下大致的作用就好了