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了,而是BootClassLoaderapp类加载器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的,但是为什么面试中,还是经常会问呢?中国人讲究学以致用,但是如果你用不到这样的场景呢?你还用学嘛?所以了解一下大致的作用就好了