Java 进阶 — 类加载机制

类加载机制是 Java 技术体系中非常核心的部分,负责把 class 文件加载到 JVM。

类加载器可以说是 Java 语言的一项创新,它是早期 Java 语言能够快速流行的重要原因之一。类加载器最初是为了满足 Java Applet 的需求而设计出来的,在今天用在浏览器上的 Java Applet 技术基本上已经被淘汰,但类加载器却在类层次划分、OSGi、程序热部署、代码加密等领域大放异彩,成为 Java 技术体系中一块重要的基石,可谓是失之桑榆,收之东隅。

——《深入理解 Java 虚拟机》

开始之前先抛出五个问题,看看能否给出准确的回答

  1. 如何启动一个 Java 应用?JVM 启动的时候是如何查找类的?
  2. 是先有的 Class 还是先有的 ClassLoader?JVM 启动的时候是如何处理 ClassLoader 的?
  3. ClassLoader 的层次结构是什么样的?双亲委派模型是什么?contextClassLoader 是什么?
  4. 类加载过程中会遇到哪些错误 Error 或者异常 Exception?NoClassDefFoundError 和 ClassNotFoundException 区别是什么?
  5. 什么情况下需要自定义 ClassLoader?如何自定义 ClassLoader?

Java 应用启动

无论是通过什么脚本启动还是在 IDE 里面直接执行,最终都是通过执行 java 命令。

IDEA 启动应用的第一行日志

mvn 命令,其实是个脚本

apache-maven-3.6.3/bin/mvn
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# ...

# Provide a "standardized" way to retrieve the CLI args that will
# work with both Windows and non-Windows executions.
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
export MAVEN_CMD_LINE_ARGS

exec "$JAVACMD" \
$MAVEN_OPTS \
$MAVEN_DEBUG_OPTS \
-classpath "${CLASSWORLDS_JAR}" \
"-Dclassworlds.conf=${MAVEN_HOME}/bin/m2.conf" \
"-Dmaven.home=${MAVEN_HOME}" \
"-Dlibrary.jansi.path=${MAVEN_HOME}/lib/jansi-native" \
"-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
${CLASSWORLDS_LAUNCHER} "$@"

来看一下 java 命令的帮助

1
2
3
4
5
$ java -help
Usage: java [-options] class [args...]
(to execute a class)
or java [-options] -jar jarfile [args...]
(to execute a jar file)

文档中对于 Java 启动时类查找的说明 https://docs.oracle.com/javase/8/docs/technotes/tools/findingclasses.html

How the Java Launcher Finds Classes

The Java launcher, java, initiates the Java virtual machine. The virtual machine searches for and loads classes in this order:

  • Bootstrap classes - Classes that comprise the Java platform, including the classes in rt.jar and several other important jar files.
  • Extension classes - Classes that use the Java Extension mechanism. These are bundled as .jar files located in the extensions directory.
  • User classes - Classes defined by developers and third parties that do not take advantage of the extension mechanism.

How the Java Launcher Finds User Classes

User classes are classes which build on the Java platform. To find user classes, the launcher refers to the user class path – a list of directories, JAR archives, and ZIP archives which contain class files.
A class file has a subpath name that reflects the class’s fully-qualified name. For example, if the class com.mypackage.MyClass is stored under /myclasses, then /myclasses must be in the user class path and the full path to the class file must be /myclasses/com/mypackage/MyClass.class. If the class is stored in an archive named myclasses.jar, then myclasses.jar must be in the user class path, and the class file must be stored in the archive as com/mypackage/MyClass.class.
The user class path is specified as a string, with a colon (:) separating the class path entries on Solaris, and a semi-colon (;) separating entries on Microsoft Windows systems. The java launcher puts the user class path string in the java.class.path system property. The possible sources of this value are:

  • The default value, “.”, meaning that user class files are all the class files in the current directory (or under it, if in a package).
  • The value of the CLASSPATH environment variable, which overrides the default value.
  • The value of the -cp or -classpath command line option, which overrides both the default value and the CLASSPATH value.
  • The JAR archive specified by the -jar option, which overrides all other values. If this option is used, all user classes must come from the specified archive.

IDE 和 maven

IDE 会通过 maven 插件,解析项目模块,计算 classpath,最终通过 -classpath 影响启动,所以修改依赖的时候需要重新加载项目。

IDE 多模块的 classpath

每个模块的 classpath 是独立计算的,在不同模块启动 main 方法的的时候 -classpath 参数是不同的,例如有两个模块 A BA 依赖 B 和第三方 C.jar , B 依赖第三方 D.jar

1
2
3
4
A
├── C.jar
└── B
└──D.jar

最终启动 A 中的 main 方法(会指向对应模块的 target/classes 目录)

1
java -classpath ${项目}/A/target/classes:${项目}/B/target/classes:${仓库}/C.jar:${仓库}/D.jar ...

如果是启动 B 中的 main 方法(此时是找不到 A 中定义类的)

1
java -classpath ${项目}/B/target/classes:${仓库}/D.jar ...

Spring 包扫描与 classpath 的关系

Spring 只能扫描类加载能加载到的类,可以简单认为就是只能加载 classpath 下的类。

Object Class ClassLoader (java.lang.*)

任何对象都是一个类的实例,那么 java.lang.String.class 是一个对象吗?(java.lang.Class extend java.lang.Object

Class 实例是通过 ClassLoader 的实例加载的,但 ClassLoader 实例本身也是一个对象,所以也会有 Class,下面这几个值是什么关系?

1
2
3
4
Class.class
ClassLoader.class
Class.class.getClassLoader()
ClassLoader.class.getClassLoader()

从 JDK 源码看类的加载

SystemDictionary 管理了所有加载的类

hotspot/src/share/vm/classfile/systemDictionary.hppsystemDictionary.hpp
1
2
3
4
5
6
7
8
class SystemDictionary : AllStatic {
//...

// Hashtable holding loaded classes.
static Dictionary* _dictionary;

//...
}

Dictionary 就是一个 Hashtable

hotspot/src/share/vm/classfile/dictionary.hppdictionary.hpp
1
2
3
class Dictionary : public TwoOopHashtable<Klass*, mtClass> {
//...
}

再来看下 Klass

hotspot/src/share/vm/oops/klass.hppklass.hpp
1
2
3
4
5
6
7
8
9

// A Klass provides:
// 1: language level class object (method dictionary etc.)
// 2: provide vm dispatch behavior for the object
// Both functions are combined into one C++ class.
// ...
class Klass : public Metadata {
// ...
}
hotspot/src/share/vm/oops/instanceKlass.hppinstanceKlass.hpp
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

// An InstanceKlass is the VM level representation of a Java class.
// It contains all information needed for at class at execution runtime.

// InstanceKlass layout:
// [C++ vtbl pointer ] Klass
// [subtype cache ] Klass
// [instance size ] Klass
// [java mirror ] Klass
// [super ] Klass
// [access_flags ] Klass
// [name ] Klass
// [first subklass ] Klass
// [next sibling ] Klass
// [array klasses ]
// [methods ]
// [local interfaces ]
// [transitive interfaces ]
// [fields ]
// [constants ]
// [class loader ]
// [source file name ]
// [inner classes ]
// [static field size ]
// [nonstatic field size ]
// [static oop fields size ]
// [nonstatic oop maps size ]
// [has finalize method ]
// [deoptimization mark bit ]
// [initialization state ]
// [initializing thread ]
// [Java vtable length ]
// [oop map cache (stack maps) ]
// [EMBEDDED Java vtable ] size in words = vtable_len
// [EMBEDDED nonstatic oop-map blocks] size in words = nonstatic_oop_map_size
// The embedded nonstatic oop-map blocks are short pairs (offset, length)
// indicating where oops are located in instances of this klass.
// [EMBEDDED implementor of the interface] only exist for interface
// [EMBEDDED host klass ] only exist for an anonymous class (JSR 292 enabled)
class InstanceKlass: public Klass {
// ...
}

在 hotspot 代码里面 Klass 是类的底层实现,类加载最终的结果就是创建一个 Klass (或其子类)对象,而 SystemDictionary 管理了所有的加载的类。

Java 虚拟机启动

来看一下 JVM 启动的时候跟 ClassLoader 相关的主要流程,从 main 方法到 java main 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
main (jdk/src/share/bin/main.c)
-> JLI_Launch (jdk/src/share/bin/java.c)
-> LoadJavaVM (jdk/src/share/bin/java.c) // 加载动态库找到hotspot实现
-> JVMInit (jdk/src/share/bin/java.c)
-> ContinueInNewThread (jdk/src/share/bin/java.c)
-> ContinueInNewThread0 (jdk/src/solaris/bin/java_md_solinux.c)
-> JavaMain (jdk/src/share/bin/java.c)
-> InitializeJVM (jdk/src/share/bin/java.c)
-> JNI_CreateJavaVM (hotspot/src/share/vm/prims/jni.cpp)
-> Threads::create_vm (hotspot/src/share/vm/runtime/thread.cpp)
-> init_globals (hotspot/src/share/vm/runtime/init.cpp)
-> universe2_init (hotspot/src/share/vm/runtime/universe.cpp)
-> Universe::genesis (hotspot/src/share/vm/runtime/universe.cpp)
-> SystemDictionary::initialize (hotspot/src/share/vm/classfile/systemDictionary.cpp)
-> SystemDictionary::initialize_preloaded_classes (hotspot/src/share/classfile/systemDictionary.cpp)
-> SystemDictionary::compute_java_system_loader (hotspot/src/share/classfile/systemDictionary.cpp)
-> LoadMainClass (jdk/src/share/bin/java.c) // 加载 MainClass
-> GetStaticMethodID (jdk/src/share/bin/java.c) // 找 main 方法
-> CallStaticVoidMethod (jdk/src/share/bin/java.c) // 调用 main 方法
hotspot/src/share/vm/classfile/systemDictionary.hppsystemDictionary.hpp
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
#define WK_KLASSES_DO(do_klass)                                                                                          \
/* well-known classes */ \
do_klass(Object_klass, java_lang_Object, Pre ) \
do_klass(String_klass, java_lang_String, Pre ) \
do_klass(Class_klass, java_lang_Class, Pre ) \
do_klass(Cloneable_klass, java_lang_Cloneable, Pre ) \
do_klass(ClassLoader_klass, java_lang_ClassLoader, Pre ) \
do_klass(Serializable_klass, java_io_Serializable, Pre ) \
do_klass(System_klass, java_lang_System, Pre ) \
do_klass(Throwable_klass, java_lang_Throwable, Pre ) \
do_klass(Error_klass, java_lang_Error, Pre ) \
do_klass(ThreadDeath_klass, java_lang_ThreadDeath, Pre ) \
do_klass(Exception_klass, java_lang_Exception, Pre ) \
do_klass(RuntimeException_klass, java_lang_RuntimeException, Pre ) \
do_klass(SecurityManager_klass, java_lang_SecurityManager, Pre ) \
do_klass(ProtectionDomain_klass, java_security_ProtectionDomain, Pre ) \
do_klass(AccessControlContext_klass, java_security_AccessControlContext, Pre ) \
do_klass(ClassNotFoundException_klass, java_lang_ClassNotFoundException, Pre ) \
do_klass(NoClassDefFoundError_klass, java_lang_NoClassDefFoundError, Pre ) \
do_klass(LinkageError_klass, java_lang_LinkageError, Pre ) \
do_klass(ClassCastException_klass, java_lang_ClassCastException, Pre ) \
do_klass(ArrayStoreException_klass, java_lang_ArrayStoreException, Pre ) \
do_klass(VirtualMachineError_klass, java_lang_VirtualMachineError, Pre ) \
do_klass(OutOfMemoryError_klass, java_lang_OutOfMemoryError, Pre ) \
do_klass(StackOverflowError_klass, java_lang_StackOverflowError, Pre ) \
do_klass(IllegalMonitorStateException_klass, java_lang_IllegalMonitorStateException, Pre ) \
do_klass(Reference_klass, java_lang_ref_Reference, Pre ) \
\
/* Preload ref klasses and set reference types */ \
do_klass(SoftReference_klass, java_lang_ref_SoftReference, Pre ) \
do_klass(WeakReference_klass, java_lang_ref_WeakReference, Pre ) \
do_klass(FinalReference_klass, java_lang_ref_FinalReference, Pre ) \
do_klass(PhantomReference_klass, java_lang_ref_PhantomReference, Pre ) \
// ...
hotspot/src/share/vm/classfile/systemDictionary.cppsystemDictionary.cpp
1
2
3
4
5
6
7
8
9
10
11
12
void SystemDictionary::initialize_preloaded_classes(TRAPS) {
// ...
initialize_wk_klasses_through(WK_KLASS_ENUM_NAME(Class_klass), scan, CHECK);
// ...
Universe::initialize_basic_type_mirrors(CHECK);
// ...
initialize_wk_klasses_through(WK_KLASS_ENUM_NAME(Reference_klass), scan, CHECK);.
// ...
initialize_wk_klasses_through(WK_KLASS_ENUM_NAME(PhantomReference_klass), scan, CHECK);
// ...
initialize_wk_klasses_until(WKID_LIMIT, scan, CHECK);
}

SystemClassLoader

除了 Bootstrap ClassLoader 之外的类加载器都是在 Java 代码里面定义的,系统类加载器 ClassLoader.getSystemClassLoader()

java.lang.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
public abstract class ClassLoader {
// ...

// The class loader for the system
// @GuardedBy("ClassLoader.class")
private static ClassLoader scl;

// Set to true once the system class loader has been set
// @GuardedBy("ClassLoader.class")
private static boolean sclSet;

// ...

@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");
sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
// ...
sclSet = true;
}
}
}
sun.misc.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
public class Launcher {
// ...

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", e);
}

// 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", e);
}

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

// ...
}
}

同时可以看到 AppClassLoader 的 parent 是 ExtClassLoader,同时 SystemClassLoader 也是在 JVM 初始化的时候进行初始化的

hotspot/src/share/vm/classfile/systemDictionary.cppsystemDictionary.cpp
1
2
3
4
5
6
7
8
9
10
11
void SystemDictionary::compute_java_system_loader(TRAPS) {
KlassHandle system_klass(THREAD, WK_KLASS(ClassLoader_klass));
JavaValue result(T_OBJECT);
JavaCalls::call_static(&result,
KlassHandle(THREAD, WK_KLASS(ClassLoader_klass)),
vmSymbols::getSystemClassLoader_name(),
vmSymbols::void_classloader_signature(),
CHECK);

_java_system_loader = (oop)result.get_jobject();
}

MainClass 的加载

jdk/src/share/bin/java.cjava.c
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
int JNICALL JavaMain(void * _args)
{
// ...
mainClass = LoadMainClass(env, mode, what);
// ...
mainID = (*env)->GetStaticMethodID(env, mainClass, "main",
"([Ljava/lang/String;)V");
// ...
/* Invoke main method. */
(*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs);
// ...
}

static jclass LoadMainClass(JNIEnv *env, int mode, char *name)
{
// ...
jclass cls = GetLauncherHelperClass(env);
// ...
// sun.launcher.LauncherHelper#checkAndLoadMain
NULL_CHECK0(mid = (*env)->GetStaticMethodID(env, cls,
"checkAndLoadMain",
"(ZILjava/lang/String;)Ljava/lang/Class;"));

str = NewPlatformString(env, name);
result = (*env)->CallStaticObjectMethod(env, cls, mid, USE_STDERR, mode, str);
// ...
return (jclass)result;
}
sun.launcher.LauncherHelper
1
2
3
4
5
6
7
8
9
10
11
12
public enum LauncherHelper {
private static final ClassLoader scloader = ClassLoader.getSystemClassLoader();

public static Class<?> checkAndLoadMain(boolean printToStderr,
int mode,
String what) {
// ...
mainClass = scloader.loadClass(cn);
// ...
return mainClass;
}
}

因此 MainClass 是由 ClassLoader.getSystemClassLoader() 加载的

一些结论

  1. 从 hotspot 的角度来讲,所有类都是通过 Klass 对象来描述的
  2. 类是一个抽象的概念
  3. Object.class != Object 类,可以说 Object.class 也是一个对象,是用来表示底层(C++)Klass 的一个对象
  4. 一定是先有类,才有对象。一切都是对象,都继承自java.lang.Object(除了基本类型)
  5. 在 JVM 加载的过程中,会预先初始化一些 Klass 对象(well known classes),来描述具体的类
  6. 最早加载的几个类(按照顺序)java.lang.Object , java.lang.String , java.lang.Class , 9 个基本类型(包含 Void.TYPE), java.lang.Cloneable , java.lang.ClassLoader
  7. 这些类是预加载,不需要有一个 java.lang.ClassLoader 的实例来加载,这个时候 JVM 都没有完全初始化
  8. 默认会有三个 ClassLoader,Bootstrap ClassLoader 是 C++实现的
  9. Class.class.getClassLoader() == null (Bootstrap ClassLoader 在 Java 层面就是 null)
  10. 主类的 ClassLoader 就是 ClassLoader.getSystemClassLoader()

Bootstrap ClassLoader 的存在是一个比较自然的事情,不需要死记硬背。

下面语句分别输出什么?

1
2
3
4
5
6
7
8
9
10
public class Test {
public static void main(String[] args) {
System.out.println(Test.class.getClassLoader());
System.out.println(javax.sql.DataSource.class.getClassLoader());
System.out.println(String.class.getClassLoader());
System.out.println(ClassLoader.getSystemClassLoader());
System.out.println(ClassLoader.getSystemClassLoader().getParent());
System.out.println(ClassLoader.getSystemClassLoader().getParent().getParent());
}
}

可以自己试一下,不同版本会有差异。

类的加载、链接、初始化

通过类的全名(binary name)获取二进制流加载到 JVM,并生成一个 java.lang.Class 对象表示这个类。


图片来源于网络

来看一下 Java 虚拟机规范关于类加载的描述 https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html

How the Java Launcher Finds Classes

The Java launcher, java, initiates the Java virtual machine. The virtual machine searches for and loads classes in this order:

  • Bootstrap classes - Classes that comprise the Java platform, including the classes in rt.jar and several other important jar files.
  • Extension classes - Classes that use the Java Extension mechanism. These are bundled as .jar files located in the extensions directory.
  • User classes - Classes defined by developers and third parties that do not take advantage of the extension mechanism.

现在我们知道,系统启动的时候,一共有三个 ClassLoader

  • Bootstrap ClassLoader (C++ null)
  • ExtClassLoader (SystemDictionary::compute_java_system_loader 时初始化)
  • AppClassLoader (SystemDictionary::compute_java_system_loader 时初始化)

后续 ExtClassLoader 指 sun.misc.Launcher$ExtClassLoader 一个的实例,AppClassLoader 指 sun.misc.Launcher$AppClassLoader 的一个的实例

类型判断

两个类型是否相等,是指全名(binary name)相等且是同一个类加载器完成加载的。包括兼容性判断

  • java.lang.Class.isAssignableFrom
  • java.lang.Class.isInstance
  • java.lang.Class.equals
  • instanceof 关键字
  • 类型转换

那么不同类加载器在加载同一个类的时候,应该如何处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Test {
public static void main(String[] args) throws ClassNotFoundException {
String name = "java.lang.Object";
Class<?> objectClass1 = Object.class;
Class<?> objectClass2 = ClassLoader.getSystemClassLoader().loadClass(name);
Class<?> objectClass3 = new URLClassLoader(new URL[0]).loadClass(name);

objectClass2.isAssignableFrom(Test.class);
objectClass3.isInstance(new Test());

objectClass1.getClassLoader(); // null
objectClass2.getClassLoader(); // ?
objectClass3.getClassLoader(); // ?
}
}

如果每个 ClassLoader 都去加载一遍 java.lang.Object 那么不同类加载加载的 Class 是不是相等的?
如果不想等对象就没有办法相互转换,那么整个程序就会表现的很奇怪,从这个角度上讲

  • AppClassLoader 去加载 Bootstrap classes 也应该是由 Bootstrap ClassLoader 加载的
  • AppClassLoader 去加载 Extension classes 也应该是由 ExtClassLoader 加载的
  • ExtClassLoader 去加载 Bootstrap classes 也应该是由 Bootstrap ClassLoader 加载的

这就是一个委托(delegation)的模型。

双亲委派

来看一下 ClassLoader 是如何加载一个类的
https://docs.oracle.com/javase/8/docs/api/java/lang/ClassLoader.html

The ClassLoader class uses a delegation model to search for classes and resources. Each instance of ClassLoader has an associated parent class loader. When requested to find a class or resource, a ClassLoader instance will delegate the search for the class or resource to its parent class loader before attempting to find the class or resource itself. The virtual machine’s built-in class loader, called the “bootstrap class loader”, does not itself have a parent but may serve as the parent of a ClassLoader instance.

这个一般就称为双亲委派模型(Parent Delegation Model )。

这个名词的翻译不好,“双”这个字不是很准确

java.lang.ClassLoader
1
2
3
4
5
6
7
8
9
10
11
12
13
public abstract class ClassLoader {

private static native void registerNatives();
static {
registerNatives();
}

// The parent class loader for delegation
// Note: VM hardcoded the offset of this field, thus all new fields
// must be added *after* it.
private final ClassLoader parent;
// ...
}

其中 AppClassLoader 的 parent 是 ExtClassLoader,ExtClassLoader 的 parent 是 Bootstrap ClassLoader(null),这个跟之前的理解是一致的

  • AppClassLoader 去加载 Bootstrap classes 也应该是由 Bootstrap ClassLoader 加载的
  • AppClassLoader 去加载 Extension classes 也应该是由 ExtClassLoader 加载的
  • ExtClassLoader 去加载 Bootstrap classes 也应该是由 Bootstrap ClassLoader 加载的

加载类对应的方法
https://docs.oracle.com/javase/8/docs/api/java/lang/ClassLoader.html#loadClass-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
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
public abstract class ClassLoader {
// ...

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.

// ...
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
// ...

/**
* Finds the class with the specified <a href="#name">binary name</a>.
* This method should be overridden by class loader implementations that
* follow the delegation model for loading classes, and will be invoked by
* the {@link #loadClass <tt>loadClass</tt>} method after checking the
* parent class loader for the requested class. The default implementation
* throws a <tt>ClassNotFoundException</tt>.
*/
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}

// ...
}


图片来源于网络

从上面的代码来看,双亲委派的实现时非常简单的。那为什么需要双亲委派?总感觉这个问题不是很好回答,因为从上面的分析来看,如果是这个 ClassLoader 的模型,那么双亲委派就是比较自然的方案

  • 保证类的唯一性
  • 避免重复加载
  • 安全,核心类不会被篡改(比如 -classpath 中包含 java.lang.Object,但委托给 Bootstrap ClassLoader 之后就只能加载默认的类)

为了保证安全性,java. 开头的类是不能去定义的(例如在 -classpath 中包含 java.lang.Object2

java.lang.ClassLoader
1
2
3
4
5
6
7
8
9
10
11
private ProtectionDomain preDefineClass(String name, ProtectionDomain pd)
{
// ...

// relies on the fact that spoofing is impossible if a class has a name
// of the form "java.*"
if ((name != null) && name.startsWith("java.")) {
throw new SecurityException("Prohibited package name: " + name.substring(0, name.lastIndexOf('.')));
}
// ...
}

这里有对上述过程更详细的解释
https://docs.oracle.com/javase/8/docs/technotes/guides/security/spec/security-spec.doc5.html

SPI 与 ContextClassLoader

我们来看一下 SPI

1
2
3
4
5
6
7
8
public class Test {
public static void main(String[] args) {
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mysql", "root", "123456");
System.out.println(conn.getClass() == com.mysql.cj.jdbc.ConnectionImpl.class);
System.out.println(com.mysql.cj.jdbc.ConnectionImpl.class.getClassLoader());
System.out.println(DriverManager.class.getClassLoader());
}
}

可能这里看上去没有什么问题,但是 DriverManager 是 Bootstrap ClassLoader 加载的,最终 ConnectionImpl 是应用自己的 classpath 下,是 AppClassLoader 加载的。

那能不能写死 SPI 就用 java.lang.ClassLoader.getSystemClassLoader() 来加载呢?

来看一下 DriverManager 是如何加载具体 Driver 实现的

java.sql.DriverManager
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class DriverManager {
// ...

static {
loadInitialDrivers();
}

// ...
private static void loadInitialDrivers() {
// ...
// SPI
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
// ...
}
}
java.util.ServiceLoader
1
2
3
4
5
6
7
8
public final class ServiceLoader<S> implements Iterable<S> {
// ...
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
// ...
}

解决这个问题是通过线程上下文类加载器 Thread.currentThread().getContextClassLoader() ,每个线程绑定一个类加载器

1
2
3
4
5
Thread.currentThread().setContextClassLoader(classloader1)
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);

Thread.currentThread().setContextClassLoader(classloader2)
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);

ContextClassLoader 并不是一个优雅的设计,不过一般在代码中很少需要关注。主要使用场景是各种框架,框架利用上下文加载器加载对应的资源或者类。

双亲委派机制的破坏

双亲委派的核心就是 parent 属性和 loadClass 方法

java.lang.ClassLoader
1
protected Class<?> loadClass(String name, boolean resolve)

protected 说明这个方法是可以覆盖的,因此双亲委派不是强制的,肯定有一些场景是可以改写这个逻辑的。

一般认为 SPI 是破坏了双亲委派机制的(委派给另外一个上下文 ClassLoader 而不 parent),此外就是覆盖 loadClass 方法破坏双亲委派的机制,具体的使用场景后面会讲。

类的初始化时机

https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html

Initialization of a class or interface consists of executing its class or interface initialization method (§2.9).

A class or interface C may be initialized only as a result of:

  • The execution of any one of the Java Virtual Machine instructions new, getstatic, putstatic, or invokestatic that references C (§new, §getstatic, §putstatic, §invokestatic). These instructions reference a class or interface directly or indirectly through either a field reference or a method reference.Upon execution of a new instruction, the referenced class is initialized if it has not been initialized already.Upon execution of a getstatic, putstatic, or invokestatic instruction, the class or interface that declared the resolved field or method is initialized if it has not been initialized already.
  • The first invocation of a java.lang.invoke.MethodHandle instance which was the result of method handle resolution (§5.4.3.5) for a method handle of kind 2 (REF_getStatic), 4 (REF_putStatic), 6 (REF_invokeStatic), or 8 (REF_newInvokeSpecial).This implies that the class of a bootstrap method is initialized when the bootstrap method is invoked for an invokedynamic instruction (§invokedynamic), as part of the continuing resolution of the call site specifier.
  • Invocation of certain reflective methods in the class library (§2.12), for example, in class Class or in package java.lang.reflect.
  • If C is a class, the initialization of one of its subclasses.
  • If C is an interface that declares a non-abstract, non-static method, the initialization of a class that implements C directly or indirectly.
  • If C is a class, its designation as the initial class at Java Virtual Machine startup (§5.2).

有且只有上述 6 种情况(且这个类没有被加载)会去加载一个类,简单来说

  • new 读取/修改静态变量(排除在常量池的)调用静态方法
  • 动态语言支持 java.lang.invoke.MethodHandle 相关
  • class library 中的一些方法,例如 Class 和 ClassLoader 的部分方法、反射(java.lang.reflect)等
  • 初始化类的某个子类的时候
  • 初始化包含默认方法接口的某个实现类的
  • Java 启动类(main Class)

其中 4 和 5 可以统一成一个递归的描述,如果要初始化一个类,需要先初始化父类和包含 default 方法的接口。
6 在实现的时候其实属于 3 的,是通过java.lang.ClassLoader.loadClass加载的(sun.launcher.LauncherHelper)。

类加载的相关异常

类加载过程,需要进行验证,比如文件格式、JAVA 版本、final 约束、可访问性约束等,具体细节就不赘述了

同时还有一个有一个非常重要的异常

1
2
3
4
5
6
7
8
9
10
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
}

在双亲委派的过程中 parent 如果加载不了某个类,需要抛出 java.lang.ClassNotFoundException(这里只 catch 了这个异常)。

public class ClassNotFoundException extends ReflectiveOperationException

Thrown when an application tries to load in a class through its string name using:

  • The forName method in class Class.
  • The findSystemClass method in class ClassLoader .
  • The loadClass method in class ClassLoader.
    but no definition for the class with the specified name could be found.

public class NoClassDefFoundError extends LinkageError

Thrown if the Java Virtual Machine or a ClassLoader instance tries to load in the definition of a class (as part of a normal method call or as part of creating a new instance using the new expression) and no definition of the class could be found.
The searched-for class definition existed when the currently executing class was compiled, but the definition can no longer be found.

java.lang.NoClassDefFoundError 是一个 Error,发生在实例化一个类的时候(类已经找到了),一般情况是这个类依赖的某些定义不存在,一般会出现在 jar 包的版本不兼容上。

类的卸载

满足如下条件,则类会被卸载

  1. 该类的所有实例(包含子类的实例)都被回收
  2. 该类的 ClassLoader 不存在任何引用
  3. 该类的 Class 对象不存在任何引用(包括相关的 Method Field 等,无法通过反射访问)

注意默认的三个类加载对应的类是不会被卸载的(ClassLoader 会一直被引用)。

自定义 ClassLoader

如何自定义 ClassLoader

一般是继承 java.net.URLClassLoader 或者直接继承 java.lang.ClassLoader,然后覆盖 findClassloadClass 方法

覆盖 findClass

这种修改一般不破坏双亲委派,会修改查找类的逻辑,实现某些动态的逻辑。可以实现但不限于以下的逻辑

  1. 从网络获取
  2. 从特定目录获取
  3. 特定目录结构的压缩包
  4. War 包 WEB-INF
  5. SpringBoot FatJar
  6. 对二进制流做预处理,例如解密或验证签名
  7. 内存直接定义 Class 文件(配合字节码的库)(动态代理)
  8. 实现热加载
  9. 类似 JSP 的技术

覆盖 loadClass

一般是修改类加载的层级结构,可以实现但不限于以下的逻辑

  1. 模块化
  2. 自定义某种类隔离的机制,例如 Tomcat

当然上面两种也可以进行组合

应用场景

SpringBoot

spring boot 应用打包之后可以直接通过 java -jar 命令启动 。

实现原理是 spring-boot-maven-plugin 在构建(repackage)的时候,会通过 org.springframework.boot.maven.RepackageMojo 打包成特定结构的 Jar 包
https://docs.spring.io/spring-boot/docs/current/maven-plugin/reference/htmlsingle/#packaging.layers

org.springframework.boot.loader.tools.Layouts.Jar
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
/**
* Executable JAR layout.
*/
public static class Jar implements RepackagingLayout {

@Override
public String getLauncherClassName() {
return "org.springframework.boot.loader.JarLauncher";
}

@Override
public String getLibraryDestination(String libraryName, LibraryScope scope) {
return "BOOT-INF/lib/";
}

@Override
public String getClassesLocation() {
return "";
}

@Override
public String getRepackagedClassesLocation() {
return "BOOT-INF/classes/";
}

@Override
public boolean isExecutable() {
return true;
}
// ...
}

可以找一个 spring boot 的 Fat Jar 解压看一下看看

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
.
├── BOOT-INF
│ ├── classes
│ │ ├── application.properties
│ │ └── com
│ │ └── example
│ │ └── demo
│ │ └── DemoApplication.class
│ ├── classpath.idx
│ ├── layers.idx
│ └── lib
│ ├── ...
│ ├── spring-boot-2.5.4.jar
│ ├── spring-boot-autoconfigure-2.5.4.jar
│ ├── spring-context-5.3.9.jar
│ └── spring-core-5.3.9.jar
├── META-INF
│ └── MANIFEST.MF
└── org
└── springframework
└── boot
└── loader
├── JarLauncher.class
├── LaunchedURLClassLoader.class
├── Launcher.class
├── ...

java -jar 命令会在 MANIFEST.MF 里面找 Main-Class 来运行,看下 META-INF/MANIFEST.MF 文件内容

META-INF/MANIFEST.MF
1
2
3
4
5
6
7
8
9
10
11
12
Manifest-Version: 1.0
Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
Implementation-Title: demo
Implementation-Version: 0.0.1-SNAPSHOT
Spring-Boot-Layers-Index: BOOT-INF/layers.idx
Start-Class: com.example.demo.DemoApplication
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Build-Jdk-Spec: 1.8
Spring-Boot-Version: 2.5.4
Created-By: Maven Jar Plugin 3.2.0
Main-Class: org.springframework.boot.loader.JarLauncher

那么是如何启动的呢?

org.springframework.boot.loader.JarLauncher
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
public class JarLauncher extends ExecutableArchiveLauncher {

static final String BOOT_INF_CLASSES = "BOOT-INF/classes/";

static final String BOOT_INF_LIB = "BOOT-INF/lib/";

public JarLauncher() {
}

protected JarLauncher(Archive archive) {
super(archive);
}

@Override
protected boolean isNestedArchive(Archive.Entry entry) {
if (entry.isDirectory()) {
return entry.getName().equals(BOOT_INF_CLASSES);
}
return entry.getName().startsWith(BOOT_INF_LIB);
}

public static void main(String[] args) throws Exception {
new JarLauncher().launch(args);
}
// ...
}
org.springframework.boot.loader.ExecutableArchiveLauncher
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
public abstract class ExecutableArchiveLauncher extends Launcher {
// ...

@Override
protected String getMainClass() throws Exception {
Manifest manifest = this.archive.getManifest();
String mainClass = null;
if (manifest != null) {
mainClass = manifest.getMainAttributes().getValue("Start-Class");
}
if (mainClass == null) {
throw new IllegalStateException("No 'Start-Class' manifest entry specified in " + this);
}
return mainClass;
}

@Override
protected List<Archive> getClassPathArchives() throws Exception {
List<Archive> archives = new ArrayList<Archive>(this.archive.getNestedArchives(new EntryFilter() {
@Override
public boolean matches(Entry entry) {
return isNestedArchive(entry);
}
}));
postProcessClassPathArchives(archives);
return archives;
}
// ...
}

org.springframework.boot.loader.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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public abstract class Launcher {

/**
* Launch the application. This method is the initial entry point that should be
* called by a subclass {@code public static void main(String[] args)} method.
* @param args the incoming arguments
* @throws Exception if the application fails to launch
*/
protected void launch(String[] args) throws Exception {
JarFile.registerUrlProtocolHandler();
ClassLoader classLoader = createClassLoader(getClassPathArchives());
launch(args, getMainClass(), classLoader);
}

/**
* Create a classloader for the specified archives.
* @param archives the archives
* @return the classloader
* @throws Exception if the classloader cannot be created
*/
protected ClassLoader createClassLoader(List<Archive> archives) throws Exception {
List<URL> urls = new ArrayList<URL>(archives.size());
for (Archive archive : archives) {
urls.add(archive.getUrl());
}
return createClassLoader(urls.toArray(new URL[urls.size()]));
}

/**
* Create a classloader for the specified URLs.
* @param urls the URLs
* @return the classloader
* @throws Exception if the classloader cannot be created
*/
protected ClassLoader createClassLoader(URL[] urls) throws Exception {
return new LaunchedURLClassLoader(urls, getClass().getClassLoader());
}

/**
* Launch the application given the archive file and a fully configured classloader.
* @param args the incoming arguments
* @param mainClass the main class to run
* @param classLoader the classloader
* @throws Exception if the launch fails
*/
protected void launch(String[] args, String mainClass, ClassLoader classLoader) throws Exception {
Thread.currentThread().setContextClassLoader(classLoader);
createMainMethodRunner(mainClass, args, classLoader).run();
}
// ...
}

因为是 Fat Jar 的模式(Jar in Jar),因此需要自己实现 LaunchedURLClassLoader 来定制类的查找逻辑。

Tomcat

Tomcat 可以同时加载多个 web 应用,并且可以热加载,支持动态加载或者卸载 web 应用。大概的实现思路

  1. Tomcat 自身的代码会有一个 ClassLoader
  2. Tomcat 为每个 Web 应用创建一个 org.apache.catalina.loader.WebappClassLoader 加载 Web 应用自己的类,且 parent 会指向 Tomcat 自身的类加载器
  3. WebappClassLoader 加载的时候
    • 会先通过 javaseClassLoader 加载,默认为 ClassLoader.getSystemClassLoader().getParent()
    • 部分类会通过 parent 来进行加载,这样应用可以共享一些类的定义(例如 javax.servlet.*org.apache.tomcat.*
    • 之后会加载 Web 应用目录下的类,因此可以实现不同应用之间的类是隔离的

上述过程会破坏双亲委派的模型。来看一下 Tomcat 的目录结构,webapps 目录下包含了所有的 Web 应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
.
├── bin
│   ├── ...
│   ├── shutdown.sh
│   └── startup.sh
├── conf
│   ├── ...
│   ├── server.xml
│   └── web.xml
├── lib
│   ├── ...
│   ├── servlet-api.jar
│   ├── tomcat-api.jar
│   └── tomcat-websocket.jar
├── logs
├── webapps
│   ├── ROOT
│   ├── docs
│   ├── examples
│   ├── host-manager
│   └── manager
└── work

Tomcat 文档关于 WebappClassLoaderBase.loadClass 的描述 https://tomcat.apache.org/tomcat-8.5-doc/api/org/apache/catalina/loader/WebappClassLoaderBase.html

org.apache.catalina.loader.WebappClassLoaderBase
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
public abstract class WebappClassLoaderBase extends URLClassLoader implements ... {
// ...

@Override
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
// ...

// (0) Check our previously loaded local class cache
// ...
// (0.1) Check our previously loaded class cache
// ...
// 之前会判断是否已经加载过
// javaseLoader 默认就是 java.lang.ClassLoader.getSystemClassLoader().getParent()
ClassLoader javaseLoader = getJavaseClassLoader();
boolean tryLoadingFromJavaseLoader;
// 判断是否应该用 javaseLoader 加载
// ...
// 通过 javaseLoader 加载
if (tryLoadingFromJavaseLoader) {
try {
clazz = javaseLoader.loadClass(name);
if (clazz != null) {
// ...
return clazz;
}
} catch (ClassNotFoundException e) {
}
}

boolean delegateLoad = delegate || filter(name, true);

// (1) Delegate to our parent if requested
if (delegateLoad) {
try {
clazz = Class.forName(name, false, parent);
if (clazz != null) {
// ...
return clazz;
}
} catch (ClassNotFoundException e) {
// Ignore
}
}

// (2) Search local repositories
try {
clazz = findClass(name);
if (clazz != null) {
// ...
return clazz;
}
} catch (ClassNotFoundException e) {
}

// (3) Delegate to parent unconditionally
if (!delegateLoad) {
try {
clazz = Class.forName(name, false, parent);
if (clazz != null) {
// ...
return clazz;
}
} catch (ClassNotFoundException e) {
}
}

throw new ClassNotFoundException(name);
}

protected boolean filter(String name, boolean isClassName) {
// ...
if (name.startsWith("javax")) {
/* 5 == length("javax") */
if (name.length() == 5) {
return false;
}
ch = name.charAt(5);
if (isClassName && ch == '.') {
/* 6 == length("javax.") */
if (name.startsWith("servlet.jsp.jstl.", 6)) {
return false;
}
if (name.startsWith("el.", 6) ||
name.startsWith("servlet.", 6) ||
name.startsWith("websocket.", 6) ||
name.startsWith("security.auth.message.", 6)) {
return true;
}
}
// ...
}
// ...
}
}

Tomcat ClassLoader 的层次结构


图片来源于网络

其他

动态语言,例如 groovy.lang.GroovyClassLoader
依赖冲突隔离 https://www.zhihu.com/question/46719811/answer/1739289578
OSGI 模块化,后来 Java 标准也引入了的模块化。
热加载也可以用多个 ClassLoader 来处理(IDEA 重新加载类,不是通过 ClassLoader 实现的)。

ClassLoader 问题排查思路

在遇到 ClassLoader 相关问题的时候,可以试试如下的思路(一般在有多个 ClassLoader 的时候)

  1. object.getClass().getClassLoader() / clazz.getClassLoader() 查看某个类的 ClassLoader,判断是否符合预期
  2. Thread.currentThread().getContextClassLoader() 获取当前线程上下文 ClassLoader,判断是否符合预期
  3. 在遇到类加载相关错误,要能想到可能是 ClassLoader 不对
  4. 在使用框架的时候,如果遇到类加载相关错误,要想到可能是 ContextClassLoader 不对
  5. object.getClass().getProtectionDomain().getCodeSource() / clazz.getProtectionDomain().getCodeSource() 可以查看某个类加载的路径(如果两个 Jar 包都有相同的类,可以看到最终是加载了哪个 Jar 包的类),判断是否符合预期
  6. 能通过 arthas 类和类加载相关的命令(scclassloaderjad 等)来排查问题

在修改 contextClassLoader 的时候,要注意恢复上下文,可以参考如下代码

1
2
3
4
5
6
7
8
ClassLoader classLoader;
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(classLoader);
// ...
} finally {
Thread.currentThread().setContextClassLoader(contextClassLoader);
}

补充:oop klass 模型

上面提到,在 hotspot 代码里面 Klass 是类的底层实现,类加载最终的结果就是创建一个 Klass 对象,oop 是描述对象各种信息的指针,oop klass 模型是 JVM(hotspot)内非常核心的模型。

hotspot/src/share/vm/oops/klass.hppklass.hpp
1
2
3
4
5
6
7
8
// A Klass provides:
// 1: language level class object (method dictionary etc.)
// 2: provide vm dispatch behavior for the object
// Both functions are combined into one C++ class.
// ...
class Klass : public Metadata {
// ...
}


图片来源于网络

在 C++层面有多个不同的类来描述这个体系,oop klass 模型这部分内容有兴趣可以自行搜索。

基本类型

int.classjava.lang.Integer.class 是不同的,int.class == java.lang.Integer.TYPE。基本类型的值本质上来说不是 oop(也没有类型指针),所以 int.class 更像是 JVM 类型系统的一个特例(create_basic_type_mirror 从名字也能看出来),这些类型也是在 JVM 初始化过程中进行加载的(SystemDictionary::initialize_preloaded_classes)。

hotspot/src/share/vm/memory/universe.cppuniverse.cpp
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
void Universe::initialize_basic_type_mirrors(TRAPS) {
assert(_int_mirror==NULL, "basic type mirrors already initialized");
_int_mirror =
java_lang_Class::create_basic_type_mirror("int", T_INT, CHECK);
_float_mirror =
java_lang_Class::create_basic_type_mirror("float", T_FLOAT, CHECK);
_double_mirror =
java_lang_Class::create_basic_type_mirror("double", T_DOUBLE, CHECK);
_byte_mirror =
java_lang_Class::create_basic_type_mirror("byte", T_BYTE, CHECK);
_bool_mirror =
java_lang_Class::create_basic_type_mirror("boolean",T_BOOLEAN, CHECK);
_char_mirror =
java_lang_Class::create_basic_type_mirror("char", T_CHAR, CHECK);
_long_mirror =
java_lang_Class::create_basic_type_mirror("long", T_LONG, CHECK);
_short_mirror =
java_lang_Class::create_basic_type_mirror("short", T_SHORT, CHECK);
_void_mirror =
java_lang_Class::create_basic_type_mirror("void", T_VOID, CHECK);

_mirrors[T_INT] = _int_mirror;
_mirrors[T_FLOAT] = _float_mirror;
_mirrors[T_DOUBLE] = _double_mirror;
_mirrors[T_BYTE] = _byte_mirror;
_mirrors[T_BOOLEAN] = _bool_mirror;
_mirrors[T_CHAR] = _char_mirror;
_mirrors[T_LONG] = _long_mirror;
_mirrors[T_SHORT] = _short_mirror;
_mirrors[T_VOID] = _void_mirror;
}

数组类型

需要注意的是数组是 JVM 内部直接生成的,是一个特殊的类型(ArrayKlass)。在定义 SomeClass[] array = new SomeClass[1] 时并不会导致 SomeClass 类的初始化。数组也是 JVM 类型系统的特例。

补充:Java 标准模块化(Java 9+)

在引入模块化之后,整个类加载机制会有一些变化,后面会单独写一遍文章来讲解。

参考

https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html
https://hg.openjdk.java.net/jdk8
https://docs.oracle.com/javase/8/docs/technotes/tools/findingclasses.html
https://docs.oracle.com/javase/8/docs/technotes/guides/security/spec/security-spec.doc5.html
《深入理解 Java 虚拟机》
https://www.cnblogs.com/mazhimazhi/

Author

Xinyu Liu

Posted on

2021-09-06

Updated on

2022-05-09


Comments