類加載機制:從Class文件到內存中Java類型的過程。各個階段時間段上能夠有重疊。 類加載是在運行期間執行的,也描述爲動態加載和動態鏈接。java
對於何時開始加載, 《Java虛擬機規範》沒有強制約束。可是嚴格規定了有且只有六種狀況,若是類沒有初始化,必須當即對類進行"初始化" (加載、鏈接必然會先執行),稱之爲主動引用:數據庫
不會觸發類初始化的幾個場景舉例:數組
//場景一 經過子類引用父類的靜態字段,不會致使子類初始化
public class SuperClass {
static { System.out.println("SuperClass init!");}
public static int value=123;
}
public class SubClass extends SuperClass{
static { System.out.println("SubClass init!");}
}
public class NoInitialization1 {
public static void main(String[] args) {
System.out.println(SubClass.value);
}
}
複製代碼
//場景二 經過數組定義引用類,不會觸發此類的初始化
public class NoInitialization2 {
public static void main(String[] args) {
SuperClass[] scarray=new SuperClass[10];
}
}
複製代碼
//場景三 引用在編譯期已被放入常量池的常量。
public class ConstClass {
static {
System.out.println("ConstClass init!");
}
public static final String HELLOWRLD="hello world";
}
public class NoInitialization3 {
public static void main(String[] args) {
System.out.println(ConstClass.HELLOWRLD);
}
}
複製代碼
對於場景三咱們查看NoInitialization3的字節碼發現,「hello world」已經在其常量池中,使用 ldc指令將常量壓入棧中。而System.out則是使用getstatic指令。這個地方不涉及到ConstClass的初始化安全
Constant pool:
#4 = String #25 // hello world
#25 = Utf8 hello world
{
public static void main(java.lang.String[]);
Code:
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #4 // String hello world
5: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
}
複製代碼
加載(loading):從靜態文件到運行時方法區。完成三件事情:markdown
使用Java虛擬機內置的引導類加載器,或者用戶自定義的類加載器。網絡
確保字節流符合《Java虛擬機規範》的約束,代碼安全性問題驗證。驗證是重要的,但不是必須的。 四個階段:數據結構
一般狀況下,爲類變量(靜態變量),分配內存並設置初始值(零值)。初值並非代碼中賦的值123。123要等到初始化階段。多線程
public static int value = 123;
複製代碼
編譯成class文件app
public static int value;
descriptor: I
flags: ACC_PUBLIC, ACC_STATIC
...
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
0: bipush 123
2: putstatic #2 // Field value:I
5: return
...
複製代碼
某些狀況下,設置初始值爲456。好比final修飾的變量。由於變量值456,會提早加入到常量池。ide
public static final int value2 = 456;
複製代碼
public static final int value2;
descriptor: I
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
ConstantValue: int 456
複製代碼
將常量池內的符號引用替換爲直接引用的過程。 好比說這種,咱們要把 #2替換成實際的類引用,若是是未加載過的類引用,又會涉及到這個類加載過程。
getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
複製代碼
執行類構造器()方法,非實例構造器()方法 。 ()方法:執行類變量賦值語句和靜態語句塊(static{})。順序爲其在源文件中順序決定。 舉例1:非法向前引用變量。 value的定義在 static{} 以後,只能賦值,不能讀取值。
public class PrepareClass {
static {
value=3;
System.out.println(value);// value: illegal forword reference
}
public static int value=123;
}
複製代碼
可是下面就能夠
public class PrepareClass {
public static int value=123;
static {
value=3;
System.out.println(value);// value: illegal forword reference
}
}
複製代碼
class文件參考
0: bipush 123
2: putstatic #2 // Field value:I
5: iconst_3
6: putstatic #2 // Field value:I
9: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
12: getstatic #2 // Field value:I
15: invokevirtual #4 // Method java/io/PrintStream.println:(I)V
18: return
複製代碼
舉例2: ()執行順序。子類初始化時,要先初始化父類
public class TestCInitClass2 {
static class Parent {
public static int A = 1;
static {
A = 2;
}
}
static class Sub extends Parent {
public static int B = A;
}
public static void main(String[] args) {
System.out.println(Sub.B);
}
}
複製代碼
輸出:
2
複製代碼
Java虛擬機必須保證()方法在多線程環境下的同步問題。
實現「經過一個類的全限定名來獲取其二進制字節流」的代碼,稱之爲「類加載器」(Class Loader)。
類與其加載器肯定了這個類在Java虛擬機中的惟一性。
三層類加載器,絕大多數Java程序會用到如下三個系統提供的類加載器進行加載:
除了以上三個還有用戶自定義的加載器,經過集成java.lang.ClassLoader類來實現。
加載Java的核心庫,native代碼實現,不繼承java.lang.ClassLoader
URL[] urls= sun.misc.Launcher.getBootstrapClassPath().getURLs();
for (URL url : urls) {
System.out.println(url);
}
結果輸出:
file:../jdk1.8.0_73.jdk/Contents/Home/jre/lib/resources.jar
file:../jdk1.8.0_73.jdk/Contents/Home/jre/lib/rt.jar
file:../jdk1.8.0_73.jdk/Contents/Home/jre/lib/sunrsasign.jar
file:../jdk1.8.0_73.jdk/Contents/Home/jre/lib/jsse.jar
file:../jdk1.8.0_73.jdk/Contents/Home/jre/lib/jce.jar
file:../jdk1.8.0_73.jdk/Contents/Home/jre/lib/charsets.jar
file:../jdk1.8.0_73.jdk/Contents/Home/jre/lib/jfr.jar
file:../jdk1.8.0_73.jdk/Contents/Home/jre/classes
複製代碼
加載Java的擴展庫,加載ext目錄下的Java類
URL[] urls= ((URLClassLoader) ClassLoader.getSystemClassLoader().getParent()).getURLs();
for (URL url : urls) {
System.out.println(url);
}
結果輸出:
file:/.../jdk1.8.0_73.jdk/Contents/Home/jre/lib/ext/sunec.jar
file:/.../jdk1.8.0_73.jdk/Contents/Home/jre/lib/ext/nashorn.jar
file:/.../jdk1.8.0_73.jdk/Contents/Home/jre/lib/ext/cldrdata.jar
file:/.../jdk1.8.0_73.jdk/Contents/Home/jre/lib/ext/jfxrt.jar
file:/.../jdk1.8.0_73.jdk/Contents/Home/jre/lib/ext/dnsns.jar
file:/.../jdk1.8.0_73.jdk/Contents/Home/jre/lib/ext/localedata.jar
file:/.../jdk1.8.0_73.jdk/Contents/Home/jre/lib/ext/sunjce_provider.jar
file:/.../jdk1.8.0_73.jdk/Contents/Home/jre/lib/ext/sunpkcs11.jar
複製代碼
加載Java應用的類。經過ClassLoader.getSystemClassLoader()來獲取。
URL[] urls= ((URLClassLoader) ClassLoader.getSystemClassLoader().getParent()).getURLs();
for (URL url : urls) {
System.out.println(url);
}
結果輸出:
file:/.../jdk1.8.0_73.jdk/Contents/Home/jre/lib/ext/sunec.jar
...
file:/.../jdk1.8.0_73.jdk/Contents/Home/lib/tools.jar
file:/.../java_sample/out/production/java_sample/ //這是咱們的應用程序
file:/Applications/IntelliJ%20IDEA.app/Contents/lib/idea_rt.jar
複製代碼
ClassLoader.loadClass
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) {
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;
}
}
複製代碼
AppClassLoader,ExtClassLoader都繼承URLClassLoader。 URLClassLoader.findClass(name)
protected Class<?> findClass(final String name)throws ClassNotFoundException {
// 一、安全檢查
// 二、根據絕對路徑把硬盤上class文件讀入內存
byte[] raw = getBytes(name);
// 三、將二進制數據轉換成class對象
return defineClass(raw);
}
複製代碼
若是咱們本身去實現一個類加載器,基本上就是繼承ClassLoader以後重寫findClass方法,且在此方法的最後調包defineClass。 ** 雙親委派確保類的全局惟一性。 例如不管哪一個類加載器須要加載java.lang.Object,都會委託給最頂端的啓動類加載器加載。
參考: 通俗易懂 啓動類加載器、擴展類加載器、應用類加載器 深刻探討 Java 類加載器
線程上下文類加載器(context class loader),能夠從java.lang.Thread中獲取。 雙親委派模型不能解決Java應用開發中遇到的全部類加載器問題。 例如,Java提供了不少服務提供者接口(Service Provider Interface,SPI),容許第三方提供接口實現。常見的SPI有JDBC,JCE,JNDI,JAXP等。SPI接口由核心庫提供,由引導類加載器加載。 而其第三方實現,由應用類加載器實現。此時SPI就找不到具體的實現了。 SPI接口代碼中使用線程上下文類加載器。線程上下文類加載器默認爲應用類加載器。