Java 类加载器

类加载器负责读取 Java 字节代码,并转换成 java.lang.Class 类的一个实例。

1
2
3
4
5
6
7
/**
A class loader is an object that is responsible for loading classes.
The class ClassLoader is an abstract class. Given the binary name of a class, a class loader should attempt to locate or generate data that constitutes a definition for the class.
A typical strategy is to transform the name into a file name and then read a "class file" of that name from a file system.
Every Class object contains a reference to the ClassLoader that defined it.
*/
public abstract class ClassLoader {}
  1. 可以通过 Class.getClassLoader() 获取类加载器;
  2. 基本数据类型及其对应的数组类型没有类加载器;
  3. 非基本数据类型和其对应的数组的类型加载器相同;

Class 类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public final class Class<T> implements java.io.Serializable, GenericDeclaration, Type, AnnotatedElement {

/** defining class loader, or null for the "bootstrap" system loader. */
private transient ClassLoader classLoader;

// The parent class loader for delegation
private final ClassLoader parent;

/**
* Returns the class loader for the class. Some implementations may use
* null to represent the bootstrap class loader. This method will return
* null in such implementations if this class was loaded by the bootstrap
* class loader.
*/
public ClassLoader getClassLoader() {
if (isPrimitive()) {
return null;
}
return (classLoader == null) ? BootClassLoader.getInstance() : classLoader;
}
}

类加载器分类

java classloader

启动类加载器

BootstrapClassLoader 负责加载 JVM 运行时核心类,这些类位于 JAVA_HOME/jre/lib/rt.jar 文件中

可以通过 java -verbose:class HelloWorld 命令查看加载的类。

扩展类加载器

ExtClassLoader 它由 sun.misc.Launcher$ExtClassLoader 实现。它负责加载 JAVA_HOME/jre/lib/ext 目录下或者由系统变量 -Djava.ext.dir 指定位路径中的类库,开发者可以直接使用标准扩展类加载器。

应用类加载器

AppClassLoader 也叫 System ClassLoader。它由 sun.misc.Launcher$AppClassLoader 实现。一般情况下是程序的默认的类加载器,通过 ClassLoader#getSystemClassLoader() 方法可以获取到该类加载器。

URLClassLoader

用于加载通过网络传输的类或资源。子类有 ExtClassLoader 和 AppClassLoader。

1
2
3
4
5
6
7
/**
This class loader is used to load classes and resources from
a search path of URLs referring to both JAR files and directories.
Any URL that ends with a '/' is assumed to refer to a directory.
Otherwise, the URL is assumed to refer to a JAR file which will be opened as needed.
*/
public class URLClassLoader extends SecureClassLoader implements Closeable {}

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
public abstract class ClassLoader {

protected Class<?> loadClass(String name, boolean resolve) {
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {//该类没有被加载过
if (parent != null) {
//说明该类是 AppClassLoader 及其子类
c = parent.loadClass(name, false);
} else {
//使用 BootstrapClassLoader 加载该类,调用 native 方法
c = findBootstrapClassOrNull(name);
}

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

/**
Returns the class with the given binary name if this loader has been recorded
by the Java virtual machine as an initiating loader of a class with that binary name.
Otherwise null is returned.
**/
protected final Class<?> findLoadedClass(String name) {
if (!checkName(name))
return null;
return findLoadedClass0(name);
}

private native final Class<?> findLoadedClass0(String name);

/**
* Returns a class loaded by the bootstrap class loader;
* or return null if not found.
*/
private Class<?> findBootstrapClassOrNull(String name) {
if (!checkName(name)) return null;
return findBootstrapClass(name);
}

// return null if not found
private native Class<?> findBootstrapClass(String name);

//由子类实现
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}

/**
Converts an array of bytes into an instance of class Class, with an optional ProtectionDomain.
If the domain is null, then a default domain will be assigned to the class
as specified in the documentation for defineClass(String, byte[], int, int).
Before the class can be used it must be resolved.
**/
protected final Class<?> defineClass(String name, byte[] b, ...)
protectionDomain = preDefineClass(name, protectionDomain);
String source = defineClassSourceLocation(protectionDomain);
Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
postDefineClass(c, protectionDomain);
return c;
}

protected final void resolveClass(Class<?> c) {
resolveClass0(c);
}

/**
Links the specified class.
This (misleadingly named) method may be used by a class loader to link a class.
If the class c has already been linked, then this method simply returns.
Otherwise, the class is linked as described in the "Execution" chapter of The Java™ Language Specification.
**/
private native void resolveClass0(Class<?> c);
}

类加载器常用方法如下:

  1. loadClass(String name, boolean resolve)
    This method is used to load the classes which are referenced by the JVM. It takes the name of the class as a parameter. This is of type loadClass(String, boolean).
  2. defineClass()
    The defineClass() method is a final method and cannot be overriden. This method is used to define a array of bytes as an instance of class. If the class is invalid then it throws ClassFormatError.
  3. findClass(String name)
    This method is used to find a specified class. This method only finds but doesn’t load the class.
  4. findLoadedClass(String name)
    This method is used to verify whether the Class referenced by the JVM was previously loaded or not.
  5. Class.forName(String name, boolean initialize, ClassLoader loader)
    This method is used to load the class as well as initialize the class. This method also gives the option to choose any one of the ClassLoaders. If the ClassLoader parameter is NULL then Bootstrap ClassLoader is used.

加载类的一般步骤:

  1. ClassLoader always follows the Delegation Hierarchy Principle.
  2. Whenever JVM comes across a class, it checks whether that class is already loaded or not.
  3. If the Class is already loaded in the method area then the JVM proceeds with execution.
  4. If the class is not present in the method area then the JVM asks the Java ClassLoader Sub-System to load that particular class, then ClassLoader sub-system hands over the control to Application ClassLoader.
  5. Application ClassLoader then delegates the request to Extension ClassLoader and the Extension ClassLoader in turn delegates the request to Bootstrap ClassLoader.
  6. Bootstrap ClassLoader will search in the Bootstrap classpath(JDK/JRE/LIB). If the class is available then it is loaded, if not the request is delegated to Extension ClassLoader.
  7. Extension ClassLoader searches for the class in the Extension Classpath(JDK/JRE/LIB/EXT). If the class is available then it is loaded, if not the request is delegated to the Application ClassLoader.
  8. Application ClassLoader searches for the class in the Application Classpath. If the class is available then it is loaded, if not then a ClassNotFoundException exception is generated.

双亲委派模型

当一个类加载器接收到类加载请求时,首先会请求其父类加载器加载,当父类加载器无法找到这个类时子类加载器才会去加载。

JVM 在加载一个 class 时会先调用 classloader 的 loadClassInternal 方法,该方法源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//ClassLoader.java

// This method is invoked by the virtual machine to load a class.
private Class<?> loadClassInternal(String name)
throws ClassNotFoundException {
// For backward compatibility, explicitly lock on 'this' when
// the current class loader is not parallel capable.
if (parallelLockMap == null) {
synchronized (this) {
return loadClass(name);
}
} else {
return loadClass(name);
}
}

Java 虚拟机是如何判定两个 Java 类是相同的?

  1. 类全名相同;
  2. 被同一个 ClassLoader 实例加载;

ClassNotFoundException 是 loadClass() 在调用 findClass() 方法中抛出的,目标类在每个类加载器的扫描路径中都不存在时,会抛出该异常。

NoClassDefFoundError 是 ClassLoader.defineClass() 方法抛出的,通过 checkName() 方法检查类的全路径是否合法。

1
2
3
4
5
6
7
8
9
10
// ClassLoader.java
// true if the name is null or has the potential to be a valid binary name
private boolean checkName(String name) {
if ((name == null) || (name.length() == 0))
return true;
if ((name.indexOf('/') != -1)
|| (!VM.allowArraySyntax() && (name.charAt(0) == '[')))
return false;
return true;
}

参考

[1] ClassLoader - oracle docs
[2] 深入探讨 Java 类加载器 - IMB
[3] Understanding Java class loading - oracle blogs
[4] sun/misc/Launcher.java