Java ClassLoader 运行机制
什么是ClassLoader
Java类要在程序中使用,需要加载到虚拟机中;执行这一过程的类就是类加载器(ClassLoader
)。ClassLoader
一般通过类的全限定名称,从网络(比如Applet
)、文件系统、内存(由代码生成的类)中加载类的二进制字节流,通过校验和链接等一系列动作之后,将类加载到虚拟机中。每一个在虚拟机中的类都可以找到对应的类加载器,通过Class.getClassLoader()
来获取得到其类加载器。
一个类不能被重复加载到同一个ClassLoader
中;同一个类被不同ClassLoader
加载,在JVM中相当于是两个不同的类。
1
2
3
4
5
6
7
DefineClassLoader dcl = new DefineClassLoader();
Class<?> clazz1 = dcl.loadClass("com.qiongsong.classloader.Test.class");
DefineClassLoader dcl2 = new DefineClassLoader();
Class<?> clazz2 = dcl2.loadClass("com.qiongsong.classloader.Test.class");
System.out.println(clazz1 == clazz2);
输出的值为 false
。
ClassLoader的分类
类加载器可以分成四种类型
- Boostrap ClassLoader 引导类加载器(C++实现),用于加载Java运行时需要的核心类,如
rt.jar
等 - ExtClassLoader 加载标准拓展类库,JDK默认的拓展类库路径位于
lib\ext
,将jar包放在此处可以被ExtClassLoader
加载 - 系统类加载器(AppClassLoader) 将系统类路径(ClassPath)下的类库加载入内存,可以通过
ClassLoader.getSystemClassLoader()
来获取对应的实例,通过System.getProperty("java.class.path")
来获取加载的文件路径 - 自定义的ClassLoader 一般通过继承
ClassLoader
,通过自定义DefineClass
来实现自定义类加载。
ClassLoader 运行机制
ClassLoader
在实现的时候使用层级委托的形式,除了引导类加载器之外的每一个ClassLoader
都具有父ClassLoader
,如下图:
在尝试进行类加载的时候,优先尝试使用父ClassLoader
进行加载,如果父ClassLoader为空(ExClassLoader
的父加载器为Boostrap ClassLoader
,但是BoostrapClassLoader
为C++实现,无法在Java代码中获取实例,因此ExtClassLoader
的parent
为null
),则尝试Boostrap ClassLoader
,最后尝试自定义类加载器,可以在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
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
//获取锁,防止同一个Class被重复加载
synchronized (getClassLoadingLock(name)) {
// 校验Class是否已经被加载,在一个ClassLoader中,一个全限定名的类只会被加载一次
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
// 实际上为一个递归,依次调用父类的loadClass方法
c = parent.loadClass(name, false);
} else {
// 尝试调用引导类加载器
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
ClassLoader
类为类加载器的抽象类,AppClassLoader
以及 ExtClassLoader
都继承自 ClassLoader
类,loadClass
为调用累加载的入。在loadClass
方法中进行向上委托而无法顺利加载类的时候就会调用findClass
方法;这个方法也是官方推荐自定义ClassLoader
时候实现的方法,在ClassLoader
中findClass
为抽象方法,需要在各个实现类中实现。使用逐级委托的调用方式,可以保证Java中的核心类可以正确加载到对应的类加载器中。如 java.lang.Object
和java.lang.Object
等核心类被Boostrap ClassLoader
加载之后,可以避免再被加载到自定义的ClassLoader
中,而使得系统中具有多个 java.lang.Object
,实际上如果使用默认的defineClass
进行核心类的加载会报出java.lang.SecurityException:Prohibited package name: java.lang
错误,这也是虚拟机安全机制的一部分。
如何自定义ClassLoader
为了保证类加载的符合委托加载机制,官方给出的建议是实现findClass
方法,实现类全限定名->二进制字节->内存中的Class对象的转换需要在protected final Class<?> defineClass(String name, byte[] b, int off, int len)
方法中实现,下面我们自定义类加载器从,文件系统中加载Class文件到虚拟机中。
自定义的类加载器,继承ClassLoader
类,实现findClass
方法
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
public byte[] loadClassData(String fileName) {
File classFile = new File(fileName);
ByteArrayOutputStream byteArrayOutputStream = null;
try {
fileInputStream = new FileInputStream(classFile);
byte[] buffer = new byte[1024];
byteArrayOutputStream = new ByteArrayOutputStream();
int readByteCount = -1;
while ((readByteCount = fileInputStream.read(buffer)) != -1) {
byteArrayOutputStream.write(buffer, 0, readByteCount);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return byteArrayOutputStream == null ? null : byteArrayOutputStream
.toByteArray();
}
@Override
public Class<?> findClass(String qualifiedName)
throws ClassNotFoundException {
if (qualifiedName.endsWith(".class")) {
qualifiedName = qualifiedName.substring(0,
qualifiedName.lastIndexOf("."));
}
String className = qualifiedName.substring(qualifiedName
.lastIndexOf(".") + 1);
// 定义文件系统目录为 D盘
String fileName = "D:\\" + className + ".class";
//将文件转换为字节流
byte[] classBytes = loadClassData(fileName);
return defineClass(qualifiedName, classBytes, 0, classBytes.length);
}
定义两个测试类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class SuperTest {
public void dosomethings() {
System.out.println(" SuperTest ClassLoader " + SuperTest.class.getClassLoader());
}
}
public class Test extends SuperTest {
public void sayHello() {
System.out.println(" Test ClassLoader " + Test.class.getClassLoader());
}
}
并将编译出来的Class文件,放在D盘的根目录中,执行下面的测试方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static void main(String[] args) throws Exception {
DefineClassLoader dcl = new DefineClassLoader();
// dcl.loadClass("com.qiongsong.classloader.SuperTest.class");
// 父类的加载依然符合委托代理加载机制
Class<?> clazz = dcl.loadClass("com.qiongsong.classloader.Test.class");
Object object = clazz.newInstance();
Method method = clazz.getMethod("sayHello");
Method method2 = clazz.getMethod("dosomethings");
method.invoke(object);
method2.invoke(object);
DefineClassLoader dcl2 = new DefineClassLoader();
Class<?> clazz2 = dcl2
.loadClass("com.qiongsong.classloader.Test.class");
Object object2 = clazz2.newInstance();
Method method3 = clazz2.getMethod("sayHello");
Method method4 = clazz2.getMethod("dosomethings");
method3.invoke(object2);
method4.invoke(object2);
System.out.println(object.getClass() == object2.getClass());
}
输出结果为
Test ClassLoader com.qiongsong.classloader.DefineClassLoader@15db9742 SuperTest ClassLoader
com.qiongsong.classloader.DefineClassLoader@15db9742 Test ClassLoader
com.qiongsong.classloader.DefineClassLoader@70dea4e SuperTest ClassLoader
com.qiongsong.classloader.DefineClassLoader@70dea4e
false
Class<?> forName(String name, boolean initialize, ClassLoader loader)
可以通过类全限定名来获取Class,如果未指定loader参数,默认使用当前调用类的类加载器。Class.forName(“Foo”)等同于 Class.forName(“Foo”, true, this.getClass().getClassLoader())
类并发加载
为保证同一个类只能在一个ClassLoader中加载一次,需要对loadClass进行同步,在上文中举例的ClassLoader
的loadClass
方法为Java1.7以及之后的实现,在Java1.6的实现为
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
protected synchronized Class<?> loadClass(String paramString, boolean paramBoolean)
throws ClassNotFoundException
{
Class localClass = findLoadedClass(paramString);
if (localClass == null) {
try {
if (this.parent != null)
localClass = this.parent.loadClass(paramString, false);
else {
localClass = findBootstrapClassOrNull(paramString);
}
}
catch (ClassNotFoundException localClassNotFoundException)
{
}
if (localClass == null)
{
localClass = findClass(paramString);
}
}
if (paramBoolean) {
resolveClass(localClass);
}
return localClass;
}
在1.6的实现中,直接在loadClass
方法加上同步原语synchronized
,同一个ClassLoader
对象只能实现串行加载,在1.7的实现为在synchronized (getClassLoadingLock(name)),getClassLoadingLock
的代码实现为:
1
2
3
4
5
6
7
8
9
10
11
protected Object getClassLoadingLock(String className) {
Object lock = this;
if (parallelLockMap != null) {
Object newLock = new Object();
lock = parallelLockMap.putIfAbsent(className, newLock);
if (lock == null) {
lock = newLock;
}
}
return lock;
}
其中parallelLockMap
在ParallelLoaders
中注册为允许并行加载的时候,会进行初始化 parallelLockMap = new ConcurrentHashMap<>()
,在getClassLoadingLock
中为每一个类名设置一个锁对象,每一个相同类名的类在加载到同一个ClassLoader
的时候,会获取同一个锁,从而实现达到了同步的效果。通过这样的设置,可以实现类加载器对不同类记性并行加载。对比1.6的对象锁而言性能有所提升。