classLoader的作用

java的类加载机制,从9开始,已经做出了更改,本文基于java9,所以会和其它的资料不太一样

  1. jvm使用classLoader加载.class文件变成class对象
  2. 每个class对象都有自己的ClassLoader对象,谁加载的它,它的ClassLoader就是谁,这个可能是native的defineClass逻辑中的方法决定的
  3. jvm使用ClassLoader+类名字来确定一个类,所以,不同ClassLoader的类无论是否一样,都不会强转成功的
  4. 一个ClassLoader只能加载一个类一次,再次加载相同名字的类,native方法会报错(只要你用了defineClass方法,但是你也必须用)

按需加载

ClassLoader按照实际的需要加载需要的类,一个类如果被关联,那么它会被ClassLoader从给定的方式找到,如果找不到,
就是ClassNotFoundException,对于java9来说,有一种很方便的方法可以看到到底加载了多少类

1
java <app_name> -Xlog:class+load=info:classloaded.txt
1
2
3
4
5
[0.001s][info][class,load] opened: /home/ss/.jdks/azul-15.0.6/lib/modules
[0.005s][info][class,load] java.lang.Object source: shared objects file
[0.005s][info][class,load] java.io.Serializable source: shared objects file
[0.005s][info][class,load] java.lang.Comparable source: shared objects file
[0.005s][info][class,load] java.lang.CharSequence source: shared objects file

随便一个helloWord,基本上都需要加载大约900多个类,包含内部类,而不会把所有的类都加载上

分层加载

jvm的规范已经定了,class文件也已经编译好了,加载更多的时候,只是从哪里找到class这个问题,对于java9

  1. bootstrap类加载器BootClassLoader,现在变成了java写的,但是为了兼容性,String.classClassLoader还是null
  2. platform类加载器PlatformClassLoader,PlatformClassLoader的parent现在不是null了,而是BootClassLoader
  3. app类加载器AppClassLoader,父加载器是platform

同样的继承体系,但是加载的时候,自己写的模块肯定是app加载的,但是jdk的代码,可能是bootstrap,platform,app加载的,当然,String,System这些核心类还是bootstrap加载的,具体的模块可以用以下代码测试出来

1
2
3
4
5
6
7
8
9
10
11
12
13
public class App {
public static void main(String[] args) {
System.out.println("String->" + String.class.getClassLoader());
System.out.println("String->" + System.class.getClassLoader());
System.out.println();
ModuleLayer layer = ModuleLayer.boot();
layer.modules().forEach(module -> {
ClassLoader classLoader = module.getClassLoader();
String classLoaderName = isNull(classLoader) ? "bootstrap" : classLoader.getName();
System.out.println(classLoaderName + ": " + module.getName());
});
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
String->null
String->null

bootstrap: java.base
bootstrap: java.datatransfer
platform: jdk.localedata
bootstrap: java.instrument
bootstrap: java.xml
app: jdk.jlink
bootstrap: java.logging
app: jdk.javadoc
bootstrap: java.rmi
bootstrap: jdk.naming.rmi
app: jdk.jdeps
app: jdk.jartool
bootstrap: java.prefs
platform: java.smartcardio
bootstrap: java.management.rmi
app: j11hello
platform: jdk.zipfs
platform: java.xml.crypto
bootstrap: java.security.sasl
bootstrap: java.management
platform: java.security.jgss
bootstrap: jdk.jfr
app: jdk.internal.opt
platform: jdk.crypto.ec
bootstrap: java.naming
app: jdk.unsupported.desktop
bootstrap: java.desktop
platform: jdk.security.auth
platform: jdk.crypto.cryptoki
platform: jdk.charsets
bootstrap: jdk.management
bootstrap: jdk.management.jfr
platform: jdk.security.jgss
platform: jdk.naming.dns
app: jdk.compiler
platform: java.compiler

debug模式下,可以通过nameToModule看到

优先父类加载(双亲委派)

类加载的时候,优先会让父类加载,然后一级级往上找,正好两层父类,所以,也叫双亲委派,经常面试中问到,其实就是个优先级问题,这样做有两个需要

  1. 确保不重复加载类,确保类的唯一性,
  2. 也顺带解决了一部分安全性,比如自己写个string,肯定就不能加载成功了,当然,jdk肯定也有其它的安全机制来保证的

自定义类加载器

jvm的类规范已经定好了,你的classpath也已经写好了,那么还为什么需要自定义类加载器呢,主要是一些框架,或者是中间件才需要,比如tomcat,有自己的lib,还有flink也有别的lib需要加载,这些时候,就需要从别的地方来加载类,还有些需求,比如你的类是加密的,这个时候,肯定也是需要自定义类加载器的

ClassLoader中,有三个方法我们需要注意loadClass,findClass,defineClass,

  1. loadClass定义了父类优先加载的机制,如果你需要打破这个机制,如flink那么你就需要重写这个方法,可以变成子类优先
  2. findClass,如果你只是需要加密解密,那么你的逻辑在这里做,就很合适了,找到对应的文件,读取成字节数组,然后解密以后,调用defineClass返回即可
  3. 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的,但是为什么面试中,还是经常会问呢?中国人讲究学以致用,但是如果你用不到这样的场景呢?你还用学嘛?所以了解一下大致的作用就好了