本文主要讲解ClassLoader的含义、jvm类加载机制、ClassLoader类解读和实例。

一、ClassLoader含义

在《深入理解JAVA虚拟机中》这么解释:

1
在类加载阶段,通过类的全限定名来获取描述此类的二进制字节流,让应用程序决定如何获取所需要的类。

二、java虚拟机类加载机制

类加载阶段

jvm类加载包括五个子阶段: 加载->准备->验证->解析->初始化, 其中使用Classloader去加载类的地方就在“加载”阶段。

类加载器和双亲委派模型

JVM类加载器分为:

(1) Bootstrap ClassLoader(启动类加载器)

负责加载JAVA_HOME/lib中的类和-Xbootclasspath指定的类

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

负责加载JAVA_HOME/lib/ext中的类

(3) Application ClassLoader(应用程序类加载器/系统类加载器),还可定义自定义类加载器。

负责加载用户类路径上的类,如果用户没有自定义类加载器,则AppClassLoader为默认类加载器。 同时,ClassLoader.getSystemClassLoader()返回的是该类。

使用ClassLoader.getParent()查看父加载器, 可以看到:

  1. 自定义类加载器(不主动设置parent)的parent是AppClassLoader;
  2. AppClassLoader的parent是ExtClassLoader;
  3. ExtClassLoader的parent是null;
  4. 同时,需要注意上述类加载器不是类继承关系。

双亲委派模型的含义是:

1
2
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载,而是将请求委派给父加载器
,只有当父加载器加载不到时,才会让子加载器加载。

这样就保证了在同一个JVM中基础类都只有一份,且每个类都和类加载器一起构成了一个层级关系。

然而,“规则是用来打破的”,有很多情况会破坏双亲委派模型,如: 涉及SPI(Service Provider Interface)的加载动作(JNDI、JDBC等)、热部署和热加载等操作。

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
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;
}
}

三、 实例

自定义ClassLoader,需要继承ClassLoader,至少重写findClass(String)方法。

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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
/**
* 自定义类加载器,只支持class文件
*/
public class MyClassLoader extends ClassLoader {

/**
* 类路径
*/
private String classPath;

/**
* 资源路径
*/
private String resourcePath;

private static MyClassLoader instance = null;

public MyClassLoader(String classPath, String resourcePath) {
this.classPath = classPath;
this.resourcePath = resourcePath;
}

/**
* 同步
*/
public static synchronized MyClassLoader getMyClassLoader(String classPath, String resourcePath) {
if (instance == null) {
instance = new MyClassLoader(classPath, resourcePath);
}
return instance;
}

/**
* 重写该方法,该方法在父类ClassLoader.loadClass(String)中调用
*/
@Override
public Class<?> findClass(String name) throws ClassNotFoundException {
// 已加载,直接使用
Class<?> clz = super.findLoadedClass(name);
if (clz != null) {
return clz;
}
// 手动加载classPath中的类
return loadMyClass(name);
}

private Class<?> loadMyClass(String name) {
// 将类名中的点改为文件分隔符,并添加.class后缀
String clzName = name.replace(".", File.separator);
clzName += ".class";
// 从文件中读
String filePath = classPath + File.separator + clzName;
File file = new File(filePath);
if (!file.exists()) {
return null;
}
FileInputStream in = null;
ByteArrayOutputStream out = null;
byte[] classBytes = null;
try {
in = new FileInputStream(file);
out = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int size = 0;
while ((size = in.read(buffer)) != -1) {
out.write(buffer, 0, size);
}
classBytes = out.toByteArray();
} catch (IOException e) {
e.printStackTrace();
} finally {
closeQuietly(in);
closeQuietly(out);
}
if (classBytes == null) {
return null;
}

return super.defineClass(name, classBytes, 0, classBytes.length);

}

/**
* 静默关闭
* @param obj
*/
private void closeQuietly(Closeable obj) {
if (obj != null) {
try {
obj.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

/**
* 重写该方法,用于获取资源
*/
@Override
public URL findResource(String name) {
String path = resourcePath + File.separator + name;
File file = new File(path);
if (!file.exists()) {
return null;
}

try {
return file.toURI().toURL();
} catch (MalformedURLException e) {
e.printStackTrace();
}
return null;
}

}

使用:

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 Test {

/**
* 根目录
*/
private static final String CLASSLOAD_PATH_DIR = "C:\\Users\\Administrator\\Desktop\\testclassload";

/**
* 类路径
*/
private static final String CLASSLOAD_PATH_CLASS = "classes";

/**
* 资源路径
*/
private static final String CLASSLOAD_PATH_RESOURCES = "\\resources";

public static void main(String[] args) throws Exception {
String classPath = CLASSLOAD_PATH_DIR + File.separator + CLASSLOAD_PATH_CLASS;
String resourcePath = CLASSLOAD_PATH_DIR + File.separator + CLASSLOAD_PATH_RESOURCES;
MyClassLoader myClassLoader = MyClassLoader.getMyClassLoader(classPath, resourcePath);

// 待加载类,不在当前工程中
String bookClzName = "com.test.Book";
Class<?> bookClz = myClassLoader.loadClass(bookClzName);


System.out.println("bookClz's classLoader is : " + bookClz.getClassLoader().getClass().getName());

// 加载后new出实例
Constructor constructor = bookClz.getConstructor(new Class[] {String.class, double.class});
Method method = bookClz.getMethod("puzzleTime");
if (!method.isAccessible()) {
method.setAccessible(true);
}
method.invoke(constructor.newInstance(new Object[] {"Book0", 20}));


}
}

运行结果:
bookClz's classLoader is : com.test.MyClassLoader
OK, you found me!

/**
* 待加载类
*/
public class Book {

private String bookName;

private double price;

public Book(String bookName, double price) {
this.bookName = bookName;
this.price = price;
}

public void puzzleTime() {
System.out.println("OK, you found me!");
}
}