本文源代碼在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
來打印類加載過程。