1. ClassLoader

文档https://docs.oracle.com/javase/8/docs/api/java/lang/ClassLoader.html

我们知道类的加载是双亲委派机制,我们先来看一个例子

1
2
3
4
5
6
7
8
9
10
public class MyTest15 {
public static void main(String[] args) {
ClassLoader loader = MyTest15.class.getClassLoader();
System.out.println(loader);
ClassLoader loader1 = loader.getParent();
System.out.println(loader1);
ClassLoader loader2 = loader1.getParent();
System.out.println(loader2);
}
}

输出

1
2
3
sun.misc.Launcher$AppClassLoader@dad5dc
sun.misc.Launcher$ExtClassLoader@16d3586
null

看了文档,写一个自定义 ClassLoader

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
/**
* @Author: cuzz
* @Date: 2019/1/28 12:39
* @Description:
*/
public class MyClassLoader extends ClassLoader{
private String classLoaderName;
private final String fileExtension = ".class";

public MyClassLoader(String classLoaderName) {
super(); // 将系统类加载当做该类加载器的父加载器
this.classLoaderName = classLoaderName;
}

public MyClassLoader(ClassLoader parent, String classLoaderName) {
super(parent); // 显示指定该类加载的父加载器
this.classLoaderName = classLoaderName;
}

@Override
protected Class<?> findClass(String className) throws ClassNotFoundException {
byte[] data = loadClassData(className);
return defineClass(className, data,0, data.length);
}

private byte[] loadClassData(String name) {
InputStream is = null;
byte[] data = null;
ByteArrayOutputStream baos = null;

try {
this.classLoaderName = this.classLoaderName.replace(".", "/");
is = new FileInputStream(new File(name, this.fileExtension));
baos = new ByteArrayOutputStream();

int ch;
while (-1 != (ch = is.read())) {
baos.write(ch);
}
data = baos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
is.close();
baos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
return data;
}

public static void main(String[] args) throws Exception{
MyClassLoader myClassLoader = new MyClassLoader("myLoader");
Class<?> clazz = myClassLoader.loadClass("com.cuzz.jvm.classloader.MyTest01");
Object o = clazz.newInstance(); // 获取实例对象
System.out.println("类加载器:" + clazz.getClassLoader());
System.out.println(o);
}
}

输出

1
2
类加载器:sun.misc.Launcher$AppClassLoader@dad5dc
com.cuzz.jvm.classloader.MyTest01@16d3586

我们编写的类加载器不起作用,因为双亲委派机制,当我们尝试使用自己编写的类加载器去加载时,它会委派自己的双亲去加载,刚好系统类加载器(应用类加载器)就能加载,所以不会使用我们自己编写的类加载器,而使用系统类加载器

如果我们把路径换一下,把项目路径下 classes 中的 MyTest01.class 文件移动在别的地方,让系统类加载器找不到,然后它就会调用我们自己编写的类加载器加载

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
public class MyClassLoader extends ClassLoader{
private String classLoaderName;
private final String fileExtension = ".class";
private String path;

public MyClassLoader(String classLoaderName) {
super(); // 将系统类加载当做该类加载器的父加载器
this.classLoaderName = classLoaderName;
}

public MyClassLoader(ClassLoader parent, String classLoaderName) {
super(parent); // 显示指定该类加载的父加载器
this.classLoaderName = classLoaderName;
}

public void setPath(String path) {
this.path = path;
}

@Override
protected Class<?> findClass(String className) throws ClassNotFoundException {
byte[] data = loadClassData(className);
return defineClass(className, data,0, data.length);
}

private byte[] loadClassData(String name) {
InputStream is = null;
byte[] data = null;
ByteArrayOutputStream baos = null;

name = name.replace(".", "\\");
try {
is = new FileInputStream(new File(this.path + name + this.fileExtension));
baos = new ByteArrayOutputStream();

int ch;
while (-1 != (ch = is.read())) {
baos.write(ch);
}
data = baos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
is.close();
baos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
return data;
}

public static void main(String[] args) throws Exception{
MyClassLoader myClassLoader = new MyClassLoader("myLoader");
// Class<?> clazz = myClassLoader.loadClass("com.cuzz.jvm.classloader.MyTest01");
String path = "C:/Users/my/Desktop/";
myClassLoader.setPath(path);
Class<?> clazz = myClassLoader.loadClass("com.cuzz.jvm.classloader.MyTest01");
Object o = clazz.newInstance(); // 获取实例对象
System.out.println("类加载器:" + clazz.getClassLoader());
System.out.println("父类加载器:" + myClassLoader.getParent());
System.out.println(o);
}
}

输出

1
2
3
类加载器:com.cuzz.jvm.classloader.MyClassLoader@16d3586
父类加载器:sun.misc.Launcher$AppClassLoader@dad5dc
com.cuzz.jvm.classloader.MyTest01@a14482

1.1 defineClass

java.lang.ClassLoader#defineClass(java.lang.String, byte[], int, int)

1
2
3
4
protected final Class<?> defineClass(String name, byte[] b, int off, int len) throws ClassFormatError {
return defineClass(name, b, off, len, null);
}

通过一个字节数组返回一个 Class 的实例

1.2 loadClass

java.lang.ClassLoader#loadClass(java.lang.String, boolean)

文档:

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
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}

if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name); // 我们只需要重写这个方法就可以

// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}

1.3 命名空间

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;每一个类加载器斗鱼自己的命名空间,命名空间由该加载器及所有父类加载器所加载的类组成,在同一个命名空间中,不会出现类的完整名字(包括类的包名)相同的两个类,在不同的命名空间中,有可能会出现类的完整名字(包括类的包名)相同的两个类

1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args) throws Exception{
MyClassLoader myClassLoader = new MyClassLoader("myLoader");
MyClassLoader myClassLoader1 = new MyClassLoader("myLoader1");
String path = "C:/Users/my/Desktop/";
myClassLoader.setPath(path);
myClassLoader1.setPath(path);
Class<?> clazz = myClassLoader.loadClass("com.cuzz.jvm.classloader.MyTest01");
Class<?> clazz1 = myClassLoader1.loadClass("com.cuzz.jvm.classloader.MyTest01");
System.out.println("clazz: " + clazz.hashCode());
System.out.println("clazz1: " + clazz1.hashCode());
}

输出

1
2
clazz: 24324022
clazz1: 21685669

如果我们给 myClassLoader1 添加一个父加载器

1
2
3
4
5
6
7
8
9
10
11
12
public static void main(String[] args) throws Exception{
MyClassLoader myClassLoader = new MyClassLoader("myLoader");
// 把 myClassLoader 当做父加载器
MyClassLoader myClassLoader1 = new MyClassLoader(myClassLoader,"myLoader1");
String path = "C:/Users/my/Desktop/";
myClassLoader.setPath(path);
myClassLoader1.setPath(path);
Class<?> clazz = myClassLoader.loadClass("com.cuzz.jvm.classloader.MyTest01");
Class<?> clazz1 = myClassLoader1.loadClass("com.cuzz.jvm.classloader.MyTest01");
System.out.println("clazz: " + clazz.hashCode());
System.out.println("clazz1: " + clazz1.hashCode());
}

输出

1
2
clazz: 10568834
clazz1: 10568834

由于父加载器已经加载过了,所以就不会加载了

1.4 类的卸载

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;由 Java 虚拟机自带的类加载器所加载的类,在虚拟机的生命周期中,始终不会被卸载。前面已经介绍过,Java 虚拟机自带的类加载器包括根类加载器、扩展类加载器和系统类加载器。Java 虚拟机本身会始终引用这些类加载器,而这些类加载器则会始终引用它们所加载类的 Class 对象,因此这些 Class 对象始终是可触及的

而由用户自定义的类加载器所加载的类是可以被卸载的

1.5类加载器命名空间深度解析

通过一个例子来分析

MyCat

1
2
3
4
5
public class MyCat {
public MyCat() {
System.out.println("MyCat is loaded by: " + this.getClass().getClassLoader());
}
}

MySample

1
2
3
4
5
6
public class MySample {
public MySample () {
System.out.println("MySample is loaded by:" + this.getClass().getClassLoader());
new MyCat ();
}
}

MyClassLoader

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
public class MyClassLoader extends ClassLoader{
private String classLoaderName;
private final String fileExtension = ".class";
private String path;

public MyClassLoader(String classLoaderName) {
super(); // 将系统类加载当做该类加载器的父加载器
this.classLoaderName = classLoaderName;
}

public MyClassLoader(ClassLoader parent, String classLoaderName) {
super(parent); // 显示指定该类加载的父加载器
this.classLoaderName = classLoaderName;
}

public void setPath(String path) {
this.path = path;
}

@Override
protected Class<?> findClass(String className) throws ClassNotFoundException {
byte[] data = loadClassData(className);
return defineClass(className, data,0, data.length);
}

private byte[] loadClassData(String name) {
InputStream is = null;
byte[] data = null;
ByteArrayOutputStream baos = null;

name = name.replace(".", "\\");
try {
is = new FileInputStream(new File(this.path + name + this.fileExtension));
baos = new ByteArrayOutputStream();

int ch;
while (-1 != (ch = is.read())) {
baos.write(ch);
}
data = baos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
is.close();
baos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
return data;
}

public static void main(String[] args) throws Exception{
MyClassLoader myClassLoader = new MyClassLoader("myLoader");
String path = "C:/Users/my/Desktop/";
myClassLoader.setPath(path);
Class<?> clazz = myClassLoader.loadClass("com.cuzz.jvm.classloader.MySample");
Object object = clazz.newInstance();
}
}


输出

1
2
3
MySample is loaded by: sun.misc.Launcher$AppClassLoader@dad5dc
MyCat is loaded by: sun.misc.Launcher$AppClassLoader@dad5dc

我们知道我们自己写的 ClassLoader 与委托父类加载器去加载,所以是系统加载器加载的

现在我们把项目下 classes 路径中的 MySample.class 和 MyCat.class 删除,并复制一份到桌面

则输出

1
2
MySample is loaded by: com.cuzz.jvm.classloader.MyClassLoader@16d3586
MyCat is loaded by: com.cuzz.jvm.classloader.MyClassLoader@16d3586

由于委托父类加载器加载不到就用自己加载器加载

如果我们只把当前类路径下 MySample.class 这给文件删掉,保留 MyCat.class 文件,则输出

1
2
MySample is loaded by: com.cuzz.jvm.classloader.MyClassLoader@16d3586
MyCat is loaded by: sun.misc.Launcher$AppClassLoader@dad5dc

我们知道 MySample 是我们自定义类加载加载出来的,MyCat 是有系统类加载加载的

1
2
3
4
5
6
7
public class MySample {
public MySample () {
System.out.println("MySample is loaded by: " + this.getClass().getClassLoader());
new MyCat ();
System.out.println(MyCat.class);
}
}

输出

1
2
3
MySample is loaded by: com.cuzz.jvm.classloader.MyClassLoader@16d3586
MyCat is loaded by: sun.misc.Launcher$AppClassLoader@dad5dc
class com.cuzz.jvm.classloader.MyCat

说明自定义类加载加载的类,可以访问系统类加载加载的类

如果我们在系统类加载的类中访问自定义类加载器加载的类

1
2
3
4
5
6
public class MyCat {
public MyCat() {
System.out.println("MyCat is loaded by: " + this.getClass().getClassLoader());
System.out.println(MySample.class);
}
}

输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
MySample is loaded by: com.cuzz.jvm.classloader.MyClassLoader@16d3586
MyCat is loaded by: sun.misc.Launcher$AppClassLoader@dad5dc
Exception in thread "main" java.lang.NoClassDefFoundError: com/cuzz/jvm/classloader/MySample
at com.cuzz.jvm.classloader.MyCat.<init>(MyCat.java:6)
at com.cuzz.jvm.classloader.MySample.<init>(MySample.java:6)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:422)
at java.lang.Class.newInstance(Class.java:442)
at com.cuzz.jvm.classloader.MyClassLoader.main(MyClassLoader.java:68)
Caused by: java.lang.ClassNotFoundException: com.cuzz.jvm.classloader.MySample
at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
... 8 more

报错,说明系统加载器加载的类不能访问自定义加载器加载的类

说明当我现在加载 MySample 这个类时,使用的是我们自己定义的类加载器,然后初始实例化这个类时,需要初始化 MyCat 这个类,所以会先委托父加载器(系统加载器)去加载

但是如果我们把当前路径下的 MyCat.class 文件删掉,保留 MySample.class 文件,则报错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Exception in thread "main" MySample is loaded by: sun.misc.Launcher$AppClassLoader@dad5dc
java.lang.NoClassDefFoundError: com/cuzz/jvm/classloader/MyCat
at com.cuzz.jvm.classloader.MySample.<init>(MySample.java:6)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:422)
at java.lang.Class.newInstance(Class.java:442)
at com.cuzz.jvm.classloader.MyClassLoader.main(MyClassLoader.java:68)
Caused by: java.lang.ClassNotFoundException: com.cuzz.jvm.classloader.MyCat
at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
... 7 more

我要加载 MySample 先委托系统类加载加载,发现能加载到,然后再想加载 MyCat 这个类,此时它会调用系统加载器的父类去加载,发现加载不到,自己也不能加载,就报错了。

通过上面的例子,我们可以得出以下结论:

  • 子类加载器所加载的类能够访问到父加载器所加载的类
  • 父类加载器所加载的类无法访问到子加载器所加载的类

2. 类加载器的双亲委托模型的好处

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;可以确保 Java 核心库的类型安全:所有的 Java 应用都至少会引用 java.lang.Object 类,也就是说在运行期,java.lang.Object 这个类会被加载到 Java 虚拟机中;如果这个加载过程是由 Java 应用自己的类加载所完成的,那么很可能就会在 JVM 中存在多个版本的 java.lang.Object 了,而这些类之间还是不兼容的,相互不可见(正是命名空间发挥着作用)。可以确保 Java 核心类库所提供的类不会被自定义的类所取代。不同的类加载器可以为相同的名称(binary name)的类创建额外的命名空间。相同的名称的类可以并存在 Java 虚拟机中,只要用不同的类加载器来加载它们即可(可是是不同的类加载器,也可以是相同类加载器的不同实例)。不同的类加载器所加载的类之间是不兼容的,就相同于在 Java 虚拟机内部创建了一个又一个相互隔离的 Java 类空间,这类技术在很多框架中都得到了实际的应用。

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;内建于 JVM 中的启动类加载器会加载 java.lang.ClassLoader 以及其他的 Java 平台类,当 JVM 启动时,一块特殊的机器码会运行,它会加载扩展类加载器和系统类加载器,这块特殊的机器码叫做启动类加载器(Bootstrap),启动类加载器并不是 Java 类,而其它加载器则都是 Java 类,启动类加载器是特定于平台的机器指令,它负责开启整个加载过程。启动类加载器还会负责加载 JRE 正常运行所需要的基本组件,这包括 java.util 与 java.lang 包中的类等等。

3. Launcher 类源码分析

前面我们分析类 ClassLoader,里面有一个静态方法 getSystemClassLoader,发现 ClassLoader 是 Launcher 中一个成员变量

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
 @CallerSensitive
public static ClassLoader getSystemClassLoader() {
initSystemClassLoader(); // 初始化
if (scl == null) {
return null;
}
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkClassLoaderPermission(scl, Reflection.getCallerClass());
}
return scl;
}

private static synchronized void initSystemClassLoader() {
if (!sclSet) {
if (scl != null)
throw new IllegalStateException("recursive invocation");
// 获取一个 Launcher 类
sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
if (l != null) {
Throwable oops = null;
// scl 表示 SystemClassLoader
scl = l.getClassLoader();
try {
scl = AccessController.doPrivileged(
new SystemClassLoaderAction(scl));
} catch (PrivilegedActionException pae) {
oops = pae.getCause();
if (oops instanceof InvocationTargetException) {
oops = oops.getCause();
}
}
...
}
}
}
}

我们在idea里边看到的 sun.misc.Launcher.getLauncher() 的实现是反编译工具给出的,oracle并没有给出源码,可以到网上查找相关代码

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
40
41
private static Launcher launcher = new Launcher();
private static String bootClassPath = System.getProperty("sun.boot.class.path");

public static Launcher getLauncher() {
return launcher;
}

private ClassLoader loader;

public Launcher() {
// Create the extension class loader
ClassLoader extcl;
try {
extcl = ExtClassLoader.getExtClassLoader();
} catch (IOException e) {
throw new InternalError(
"Could not create extension class loader");
}

// Now create the class loader to use to launch the application
try {
loader = AppClassLoader.getAppClassLoader(extcl);
} catch (IOException e) {
throw new InternalError(
"Could not create application class loader");
}

// Also set the context class loader for the primordial thread.
Thread.currentThread().setContextClassLoader(loader);

// Finally, install a security manager if requested
String s = System.getProperty("java.security.manager");
......
}

/*
* Returns the class loader used to launch the main application.
*/
public ClassLoader getClassLoader() {
return loader;
}

可以看到 Launcher 类初始化时,先初始化了个 ExtClassLoader,然后又初始化了个 AppClassLoader,然后把ExtClassLoader 作为 AppClassLoader的父 loader,ExtClassLoader 没有指定父类,即表明,父类是BootstrapClassLoader。把初始化 的AppClassLoader 作为全局变量保存起来,并设置到当前线程contextClassLoader,每个线程实例可以设置一个 contextClassLoader 。

先回到 initSystemClassLoader 方法中,有这一段代码

1
2
3
4
5
6
7
8
9
try {
scl = AccessController.doPrivileged(
new SystemClassLoaderAction(scl));
} catch (PrivilegedActionException pae) {
oops = pae.getCause();
if (oops instanceof InvocationTargetException) {
oops = oops.getCause();
}
}

我们把系统加载传入到 doPrivileged 中的 SystemClassLoaderAction 中又返回了系统加载器,我们看看 SystemClassLoaderAction 这个类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class SystemClassLoaderAction
implements PrivilegedExceptionAction<ClassLoader> {
private ClassLoader parent;

SystemClassLoaderAction(ClassLoader parent) {
this.parent = parent;
}

public ClassLoader run() throws Exception {
String cls = System.getProperty("java.system.class.loader");
if (cls == null) {
return parent;
}

Constructor<?> ctor = Class.forName(cls, true, parent)
.getDeclaredConstructor(new Class<?>[] { ClassLoader.class });
ClassLoader sys = (ClassLoader) ctor.newInstance(
new Object[] { parent });
Thread.currentThread().setContextClassLoader(sys);
return sys;
}
}

这块逻辑的作用是看看是否设置了系统属性 java.system.class.loader,即自定义的系统类加载器,如果设置了那么实例化自定义的系统类加载器返回,替代之前获取的系统类加载器,如果没有设置直接返回默认的系统类加载器。

4. Class.forName()

java.lang.Class#forName(java.lang.String, boolean, java.lang.ClassLoader)

文档:


代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static Class<?> forName(String name, boolean initialize,
ClassLoader loader)
throws ClassNotFoundException
{
Class<?> caller = null;
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
// Reflective call to get caller class is only needed if a security manager
// is present. Avoid the overhead of making this call otherwise.
// 获取调用 forName 方法的的那个类
caller = Reflection.getCallerClass();
if (sun.misc.VM.isSystemDomainLoader(loader)) {
ClassLoader ccl = ClassLoader.getClassLoader(caller);
if (!sun.misc.VM.isSystemDomainLoader(ccl)) {
sm.checkPermission(
SecurityConstants.GET_CLASSLOADER_PERMISSION);
}
}
}
return forName0(name, initialize, loader, caller);
}

5. 线程上下文类加载器分析与实现

5.1 前言

看一个程序来一下感性的认识:

1
2
3
4
5
6
public class MyTest24 {
public static void main(String[] args)
System.out.println(Thread.currentThread().getContextClassLoader());
System.out.println(Thread.class.getClassLoader());
}
}

这个程序的输出是:

1
2
sun.misc.Launcher$AppClassLoader@18b4aac2
null

解析:

第一行当前的线程是运行MyTest24 的线程,而MyTest24 是由系统类加载器加载,所以打印的是系统类加载器

第二行Thread类是java核心库的类,是由启动类加载器加载,所以打印 null

当前类加载器(Current ClassLoader)

每个类都会使用自己的类加载器(即加载自身的类加载器) 来去加载其他类(指的是所依赖的类) ,如果ClassA引用了ClassY,那么ClassX的类加载器就会加载ClassY(前提是ClassY尚未被加载)

线程上下文加载器(Context ClassLoader)

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;线程上下文类加载器是从jdk1.2开始引入的,类Thread中的 getContextCLassLoader() 与setContextClassLoader(ClassLoader classloader) 分别用来获取和设置上下文类加载器,如果没有通过与setContextClassLoader(ClassLoader classloader)进行设置的话,线程将继承其父线程的上下文类加载器。

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Java应用运行时的初始线程的上下文加载器是系统类加载器,在线程中运行的代码可以通过该类加载器来加载类与资源。

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;我们在使用jdbc的时候,不同的数据库的驱动都是由每个厂商自己去实现,开发者在使用的时候,只需要把驱动jar包 ,放到当前path下边就可以了,这些驱动是由系统类加载器加载,而 java.sql 下边的一些Class在使用的时候不可避免的 ,要去使用厂商自定义的实现的逻辑,但是这些 java.sql 下的类的加载器是由启动类加载器完成的加载,由于父加载器(启动类加载器)加载的类无法访问子加载器(系统类加载器或者应用类加载器)加载的类,所以就无法在有些 java.sql 的类去访问具体的厂商实现,这个是双亲委托模型尴尬的一个局面。

线程上下文加载器的重要性:

SPI (Service Provider Interface)

父 ClassLoader 可以使用当前线程 Thread.currentThread().getContextClassLoader() 所指定的 classloader 加载的类。 这就改变了父 ClassLoader 不能使用子 ClassLoader 或是其他没有直接父子关系的 CLassLoader 加载的类的情况,即改变了双亲委托模型。

线程上下文加载器就是当前线程的 Current ClassLoader 在双亲委托模型下,类加载器由下至上的,即下层的类加载器会委托上层进行加载。但是对于 SPI 来说,有些接口是 java 核心库所提供的,而java核心库是由启动类加器来加载的,而这些接口的实现来自于不同的jar包(厂商提供),java 的启动类加载器是不会加载其他来源的jar包,这样传统的双亲委托模型就无法满足SPI的要求,而通过给当前线程设置上下文加载器,就可以设置上下文类加载器来实现对于接口实现类的加载。

5.2 线程上下文的一般使用模式

线程上下文的一般使用模式分为3步,获取、使用和还原,下面是伪代码

1
2
3
4
5
6
7
8
9
10
11
// 获取
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
try {
// 使用
Thread.currentThread().setContextClassLoader(targetClassLoader);
method();
} finally {
// 还原
Thread.currentThread().setContextClassLoader(classLoader);
}

method 里面调用了 Thread.currentThread().getContextClassLoader(),获取当前线程上下文类加载器做某些事情。如果一个类由类加载器 A 加载,那么这个类的依赖也是有相同的类加载器加载的(如果该依赖类之前没有加载过的话),ContextClassLoader 的作用就是为了破坏 Java 的类加载委托机制。

当高层提供了统一的接口让底层去实现,同时又要在高层加载(或实例化)底层类时,就必须要通过线程上下文类加载器来帮助高层的 ClassLoader 找到并加载该类。

5.3 ServiceLoader

我们先引入驱动依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
group 'com.cuzz.jvm'
version '1.0'

apply plugin: 'java'

sourceCompatibility = 1.8

repositories {
mavenCentral()
}

dependencies {
compile (
"mysql:mysql-connector-java:5.1.34"
)
}

我们先来看一个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* @Author: zzxx
* @Date: 2019/2/1 14:46
* @Description:
*/
public class MyTest26 {
public static void main(String[] args) {
ServiceLoader<Driver> serviceLoader = ServiceLoader.load(Driver.class);
Iterator<Driver> iterator = serviceLoader.iterator();

while(iterator.hasNext()){
Driver driver = iterator.next();
System.out.println("driver: "+driver.getClass() + "loader: "+ driver.getClass().getClassLoader() );
}

System.out.println("当前线程上下文类加载器: " + Thread.currentThread().getContextClassLoader());

System.out.println("ServiceLoader的类加载器: "+ServiceLoader.class.getClassLoader());

}
}

输出:

1
2
3
4
driver: class com.mysql.jdbc.Driverloader: sun.misc.Launcher$AppClassLoader@dad5dc
driver: class com.mysql.fabric.jdbc.FabricMySQLDriverloader: sun.misc.Launcher$AppClassLoader@dad5dc
当前线程上下文类加载器: sun.misc.Launcher$AppClassLoader@dad5dc
ServiceLoader的类加载器: null

我们可以看到 ServiceLoader 找到了 mysql 的两个驱动,这两个驱动都是由系统类加载器加载的,当前线程的上下文加载器默认也是系统类加载器,ServiceLoader是由启动类加载器加载,但是程序是怎样找到 mysql 的两个驱动的呢?我们没有在程序里边设置任何的属性或者路径之类的东西让程序能找到 mysql 的驱动,那么我们只能研究一下 ServiceLoader 的源码和文档看一下他们的原理:

我们先看源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public final class ServiceLoader<S> implements Iterable<S> {
// 前缀
private static final String PREFIX = "META-INF/services/";

// The class or interface representing the service being loaded
private final Class<S> service;

// The class loader used to locate, load, and instantiate providers
private final ClassLoader loader;

// The access control context taken when the ServiceLoader is created
private final AccessControlContext acc;

// Cached providers, in instantiation order
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

// The current lazy-lookup iterator
private LazyIterator lookupIterator;

}

该类中有个常量 PREFIX ,根据文档我们可以知道这是一个目录,我们看看 mysql-connnector-java 中也有

其下的文件名字就是服务的名字,比如数据库驱动的服务是java.sql.Drive,我们在mysql的jar包下可以看到这个文件,文件里边的内容是具体的实现类的全限定名:

1
2
com.mysql.jdbc.Driver
com.mysql.fabric.jdbc.FabricMySQLDriver

与前面打印出来的驱动是一样的

ServiceLoader 是由启动类加载器加载的,为什么 mysql 的驱动是由系统类加载器加载呢?

前面代码中 ServiceLoader serviceLoader = ServiceLoader.load(Driver.class); 这段代码是怎么起作用的呢,跟进源码

1
2
3
4
5
6
7
8
9
10
11
public static <S> ServiceLoader<S> load(Class<S> service) {
// 获取当前上下文类加载,并使用上下文类加载器去加载
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}

public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader) {
// 调用一个构造方法
return new ServiceLoader<>(service, loader);
}

既然 ServiceLoader 是由启动类加载器加载,那么 ServiceLoader 里边的类都会用启动类加载器去加载,但是呢我们的 mysql 驱动不在启动类加载器加载的目录下边,我们的 mysql 驱动位于 classpath 下边,无法用启动类加载器加载,这个时候,我们可以看到 load 方法使用了线程上下文加载器,线程上下文加载器默认是系统类加载器

我们来看看这个构造方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
   private ServiceLoader(Class<S> svc, ClassLoader cl) {
service = Objects.requireNonNull(svc, "Service interface cannot be null");
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
reload();
}
// 调用reload() 方法
public void reload() {
// 清空缓存 providers = new LinkedHashMap<>();
providers.clear();
// 懒加载
lookupIterator = new LazyIterator(service, loader);
}

LazyIterator 类

java.util.ServiceLoader.LazyIterator

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92

private class LazyIterator implements Iterator<S> {
Class<S> service;
ClassLoader loader;
Enumeration<URL> configs = null;
Iterator<String> pending = null;
String nextName = null;

private LazyIterator(Class<S> service, ClassLoader loader) {
this.service = service;
this.loader = loader;
}

private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
String fullName = PREFIX + service.getName();
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
configs = loader.getResources(fullName);
} catch (IOException x) {
fail(service, "Error locating configuration files", x);
}
}
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
}
pending = parse(service, configs.nextElement());
}
nextName = pending.next();
return true;
}

private S nextService() {
if (!hasNextService())
throw new NoSuchElementException();
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
fail(service,
"Provider " + cn + " not found");
}
if (!service.isAssignableFrom(c)) {
fail(service,
"Provider " + cn + " not a subtype");
}
try {
S p = service.cast(c.newInstance());
providers.put(cn, p);
return p;
} catch (Throwable x) {
fail(service,
"Provider " + cn + " could not be instantiated",
x);
}
throw new Error(); // This cannot happen
}

public boolean hasNext() {
if (acc == null) {
return hasNextService();
} else {
PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
public Boolean run() { return hasNextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}

public S next() {
if (acc == null) {
return nextService();
} else {
PrivilegedAction<S> action = new PrivilegedAction<S>() {
public S run() { return nextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}

public void remove() {
throw new UnsupportedOperationException();
}
}

这样就把驱动加载出来了,则前面代码输出

1
2
3
4
driver: class com.mysql.jdbc.Driverloader: sun.misc.Launcher$AppClassLoader@dad5dc
driver: class com.mysql.fabric.jdbc.FabricMySQLDriverloader: sun.misc.Launcher$AppClassLoader@dad5dc
当前线程上下文类加载器: sun.misc.Launcher$AppClassLoader@dad5dc
ServiceLoader的类加载器: null

如果我们把前面代码改一下,设置当前线程的上下文类加载器为扩展类加载器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MyTest26 {
public static void main(String[] args) {
// 把当前线程设置为扩展类加载器
Thread.currentThread().setContextClassLoader(MyTest26.class.getClassLoader().getParent());
ServiceLoader<Driver> serviceLoader = ServiceLoader.load(Driver.class);
Iterator<Driver> iterator = serviceLoader.iterator();

while(iterator.hasNext()){
Driver driver = iterator.next();
System.out.println("driver: "+driver.getClass() + "loader: "+ driver.getClass().getClassLoader() );
}

System.out.println("当前线程上下文类加载器: " + Thread.currentThread().getContextClassLoader());

System.out.println("ServiceLoader的类加载器: "+ServiceLoader.class.getClassLoader());
}
}

则输出

1
2
当前线程上下文类加载器: sun.misc.Launcher$ExtClassLoader@a14482
ServiceLoader的类加载器: null

可以看到循环没有去执行,上下文类加载器是扩展类加载器没啥问题,因为系统类加载器的上级是扩展类加载器,但是为什么循环是空的呢?原因就是扩展类加载器无法加载 classpath下边的类,mysql 的 jar 包是放在 classpath下边的。