本文源代码在Github。html
本文仅为我的笔记,不该做为权威参考。java
原文git
在前一篇文章初步了解ClassLoader里提到了委托模型(又称双亲委派模型),解释了ClassLoader hierarchy(层级)处理类加载的过程。那么class文件是如何变成Class对象的呢?github
Class加载分为这几步:bootstrap
连接(Linking)数组
注: 前面说了数组类是虚拟机直接建立的,以上过程不适用于数组类。安全
什么时候会触发一个类的加载?oracle
Java Language Specification - 12.1.1. Load the Class Test:app
The initial attempt to execute the methodmain
of classTest
discovers that the classTest
is not loaded - that is, that the Java Virtual Machine does not currently contain a binary representation for this class. The Java Virtual Machine then uses a class loader to attempt to find such a binary representation.
也就是说,当要用到一个类,JVM发现尚未包含这个类的二进制形式(字节)时,就会使用ClassLoader尝试查找这个类的二进制形式。jvm
咱们知道ClassLoader委托模型,也就是说实际触发加载的ClassLoader和真正加载的ClassLoader可能不是同一个,JVM将它们称之为initiating loader
和defining loader
(Java Virtual Machine Specification - 5.3. Creation and Loading):
A class loaderL
may create C by defining it directly or by delegating to another class loader. IfL
creates C directly, we say thatL
defines C or, equivalently, thatL
is the defining loader of C.When one class loader delegates to another class loader, the loader that initiates the loading is not necessarily the same loader that completes the loading and defines the class. If
L
creates C, either by defining it directly or by delegation, we say thatL
initiates loading of C or, equivalently, thatL
is an initiating loader of C.
那么当A类使用B类的时候,B类使用的是哪一个ClassLoader呢?
Java Virtual Machine Specification - 5.3. Creation and Loading:
The Java Virtual Machine uses one of three procedures to create class or interface C denoted by N:
If
N
denotes a nonarray class or an interface, one of the two following methods is used to load and thereby create C:
- If D was defined by the bootstrap class loader, then the bootstrap class loader initiates loading of C (§5.3.1).
- If D was defined by a user-defined class loader, then that same user-defined class loader initiates loading of C (§5.3.2).
- Otherwise
N
denotes an array class. An array class is created directly by the Java Virtual Machine (§5.3.3), not by a class loader. However, the defining class loader of D is used in the process of creating array class C.注:上文的C和D都是类,N则是C的名字。
也就说若是D用到C,且C尚未被加载,且C不是数组,那么:
再总结一下就是:若是D用到C,且C尚未被加载,且C不是数组,那么C的initiating loader就是D的defining loader。
用下面的代码观察一下:
// 把这个项目打包而后放到/tmp目录下 public class CreationAndLoading { public static void main(String[] args) throws Exception { // ucl1的parent是bootstrap class loader URLClassLoader ucl1 = new NamedURLClassLoader("user-defined 1", new URL[] { new URL("file:///tmp/classloader.jar") }, null); // ucl1是ucl2的parent URLClassLoader ucl2 = new NamedURLClassLoader("user-defined 2", new URL[0], ucl1); Class<?> fooClass2 = ucl2.loadClass("me.chanjar.javarelearn.classloader.Foo"); fooClass2.newInstance(); } } public class Foo { public Foo() { System.out.println("Foo's classLoader: " + Foo.class.getClassLoader()); System.out.println("Bar's classLoader: " + Bar.class.getClassLoader()); } } public class NamedURLClassLoader extends URLClassLoader { private String name; public NamedURLClassLoader(String name, URL[] urls, ClassLoader parent) { super(urls, parent); this.name = name; } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { System.out.println("ClassLoader: " + this.name + " findClass(" + name + ")"); return super.findClass(name); } @Override public Class<?> loadClass(String name) throws ClassNotFoundException { System.out.println("ClassLoader: " + this.name + " loadClass(" + name + ")"); return super.loadClass(name); } @Override public String toString() { return name; } }
运行结果是:
ClassLoader: user-defined 2 loadClass(me.chanjar.javarelearn.classloader.Foo) ClassLoader: user-defined 1 findClass(me.chanjar.javarelearn.classloader.Foo) ClassLoader: user-defined 1 loadClass(java.lang.Object) ClassLoader: user-defined 1 loadClass(java.lang.System) ClassLoader: user-defined 1 loadClass(java.lang.StringBuilder) ClassLoader: user-defined 1 loadClass(java.lang.Class) ClassLoader: user-defined 1 loadClass(java.io.PrintStream) Foo's classLoader: user-defined 1 ClassLoader: user-defined 1 loadClass(me.chanjar.javarelearn.classloader.Bar) ClassLoader: user-defined 1 findClass(me.chanjar.javarelearn.classloader.Bar) Bar's classLoader: user-defined 1
能够注意到Foo
的initiating loader是user-defined 2,可是defining loader是user-defined 1。而Bar
的initiating loader与defining loader则直接是user-defined 1,绕过了user-defined 2。观察结果符合预期。
验证类的二进制形式在结构上是否正确。
为类建立静态字段,而且为这些静态字段初始化默认值。
JVM在运行时会为每一个类维护一个run-time constant pool,run-time constant pool构建自类的二进制形式里的constant_pool
表。run-time constant pool里的全部引用一开始都是符号引用(symbolic reference)(见Java Virutal Machine Specification - 5.1. The Run-Time Constant Pool)。符号引用就是并不是真正引用(即引用内存地址),只是指向了一个名字而已(就是字符串)。解析阶段作的事情就是将符号引用转变成实际引用)。
Java Virutal Machine Specification - 5.4. Linking:
This specification allows an implementation flexibility as to when linking activities (and, because of recursion, loading) take place, provided that all of the following properties are maintained:
- A class or interface is completely loaded before it is linked.
- A class or interface is completely verified and prepared before it is initialized.
也就是说仅要求:
因此对于解析的时机JVM Spec没有做出太多规定,只说了如下JVM指令在执行以前须要解析符号引用:_anewarray_, checkcast_, _getfield_, _getstatic_, _instanceof_, _invokedynamic_, _invokeinterface_, _invokespecial_, _invokestatic_, _invokevirtual_, _ldc_, _ldc_w_, _multianewarray_, _new_, _putfield 和 putstatic 。
看不懂不要紧,大体意思就是,用到字段、用到方法、用到静态方法、new类等时候须要解析符号引用。
若是直接赋值的静态字段被 final 所修饰,而且它的类型是基本类型或字符串时,那么该字段便会被 Java 编译器标记成常量值(ConstantValue),其初始化直接由 Java 虚拟机完成。除此以外的直接赋值操做,以及全部静态代码块中的代码,则会被 Java 编译器置于同一方法中,并把它命为 <clinit>
(class init)。
JVM 规范枚举了下述类的初始化时机是:
注意:这里没有提到new 数组的状况,因此new 数组的时候不会初始化类。
同时类的初始化过程是线程安全的,下面是一个利用上述时机4和线程安全特性作的延迟加载的Singleton的例子:
public class Singleton { private Singleton() {} private static class LazyHolder { static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance() { return LazyHolder.INSTANCE; } }
这种作法被称为Initialization-on-demand holder idiom。
Java Virutal Machine Specification - 5.3.1. Loading Using the Bootstrap Class Loader:
If no purported representation of C is found, loading throws an instance of
ClassNotFoundException
.
Java Virutal Machine Specification - 5.3.2. Loading Using a User-defined Class Loader:
When the
loadClass
method of the class loaderL
is invoked with the nameN
of a class or interface C to be loaded,L
must perform one of the following two operations in order to load C:
- The class loader
L
can create an array of bytes representing C as the bytes of a ClassFile structure (§4.1); it then must invoke the methoddefineClass
of class ClassLoader. Invoking defineClass causes the Java Virtual Machine to derive a class or interface denoted byN
usingL
from the array of bytes using the algorithm found in §5.3.5.- The class loader
L
can delegate the loading of C to some other class loader L'. This is accomplished by passing the argumentN
directly or indirectly to an invocation of a method onL'
(typically theloadClass
method). The result of the invocation is C.In either (1) or (2), if the class loader
L
is unable to load a class or interface denoted byN
for any reason, it must throw an instance of ClassNotFoundException.
因此,ClassNotFoundException
发生在【加载阶段】:
ClassNotFoundException
ClassNotFoundException
Java Virtual Machine Specification - 5.3. Creation and Loading
If the Java Virtual Machine ever attempts to load a class C during verification (§5.4.1) or resolution (§5.4.3) (but not initialization (§5.5)), and the class loader that is used to initiate loading of C throws an instance ofClassNotFoundException
, then the Java Virtual Machine must throw an instance ofNoClassDefFoundError
whose cause is the instance ofClassNotFoundException
.(A subtlety here is that recursive class loading to load superclasses is performed as part of resolution (§5.3.5, step 3). Therefore, a
ClassNotFoundException
that results from a class loader failing to load a superclass must be wrapped in aNoClassDefFoundError
.)
Java Virtual Machine Specification - 5.3.5. Deriving a Class from a class File Representation
Otherwise, if the purported representation does not actually represent a class namedN
, loading throws an instance ofNoClassDefFoundError
or an instance of one of its subclasses.
Java Virtual Machine Specification - 5.5. Initialization
If the Class object for C is in an erroneous state, then initialization is not possible. ReleaseLC
and throw aNoClassDefFoundError
.
因此,NoClassDefFoundError
发生在:
ClassNotFoundException
,那么就要抛出NoClassDefFoundError
,cause 是ClassNotFoundException
。ClassNotFoundException
也必须包在NoClassDefFoundError
里。NoClassDefFoundError
NoClassDefFoundError
能够在JVM启动时添加-verbose:class
来打印类加载过程。