# 三、類加載機制
## 類加載過程
一個類從被加載到虛擬機內存開始到卸載出內存為止,經歷的過程如下:
:-: 
與C語言這種編譯型語言不同的是,Java的加載、鏈接和初始化都是在程序運行期間動態執行的。因此Java才被稱為解釋型語言。
### 加載階段
將類的字節碼內容加載到方法區中時,JVM內部會使用一個C++的一個instanceKlass結構體來描述Java的類,其主要的字段有
1. \_java\_mirror:java的類鏡像,是C++的結構和Java的類結構之間的橋梁,作用是將Klass暴露給Java使用。
2. \_super:父類。
3. \_fields:成員變量。
4. \_methods:方法。
5. \_constants:常量池。
6. \_class\_loader:類加載器。
7. \_vtable:虛方法表。
8. \_itable:接口方法表。
如果這個類的父類還沒有被加載,就會先加載父類;加載和鏈接階段可能是交替運行的。
> 注意:instanceKlass這樣的元數據是存儲在方法區的,當\_java\_mirror是存儲在堆中。
:-: 
### 鏈接階段
鏈接階段分為三個步驟:
1. 驗證:驗證類是否符合JVM規范,同時進行安全性檢查。
可以使用二進制編輯器修改class文件的魔數,在控制臺運行看看是否正確。
2. 準備:為靜態變量分配空間,設置默認值。
* static變量在JDK7之前存儲于instanceKlass末尾,從JDK7開始,存儲于\_java\_mirror末尾。
* static變量分配空間和賦值分為兩個步驟,分配空間在準備階段(final修飾的是在分配空間階段,使用了new關鍵字的發生在初始化階段),賦值在初始化階段完成(cinit<>方法)。
3. 解析:將常量池中的符號引用解析為直接引用。

類D只有用到的時候才會被加載和解析,HDBS中會有JVM\_CONSTANT\_UnresolvedClass修飾,表示其為未經解析的。
### 初始化階段
《Java虛擬機規范》中對初始化階段有嚴格的要求,遇到下面4個字節碼指令的時候如果類型還沒有進行初始化則必須先進行初始化。
~~~
?new、getstatic、putstatic、invokestatic
~~~
> PS:初始化階段會調用初始化方法,虛擬機會保證這個類的初始化方法的線程安全。
這些指令出現的場景有:
* new 關鍵字實例化一個對象。
* 讀取或者設置一個類型的靜態字段,注意該字段沒有被final修飾。
* 調用一個類型的靜態方法。
> PS:這里的類型指的是類或者接口。
其他的初始化時機:
1. 虛擬機啟動時,main方法所在的類會首先被初始化。
2. 使用java.lang.reflect包的方法對類型進行`反射`調用的時候,如果類型還沒有初始化則會提前進行初始化。
3. 子類初始化的時候其父類也會初始化。
4. 如果一個接口中定義了JDK8之后的默認方法(default修飾),其實現類發生了初始化,則這個接口要在其之前進行初始化。
不會導致初始化的情況:
1. 訪問類的static final靜態常量(基本類型和字符串)不會觸發初始化,這部分的內容在鏈接階段就初始化了。
2. 類對象.class不會觸發初始化。
3. 創建該類的數組不會觸發初始化。
4. 類加載的loadClass方法不會觸發初始化。
5. Class.forName的參數2為false時不會觸發初始化。
6. 子類調用父類的靜態變量時不會觸發子類的初始化。
~~~
?package classloading;
??
?import javafx.util.converter.ShortStringConverter;
??
?/**
? * 演示不會進行類初始化的情況
? */
??
?public class NotInitialization {
??
??
? ? ?public static void main(String[] args) {
? ? ? ? ?// 1. 子類調用父類的靜態變量
? ? ? ? ?System.out.println(SubClass.a);
? ? ? ? ?// 輸出結果:super init
? ? ? ? ?// 2. 調用常量
? ? ? ? ?System.out.println(SubClass.str);
? ? ? ? ?// 輸出:hello
? ? ? ? ?System.out.println(SubClass.superClass);
? ? ? ? ?// 輸出:sub init
? ? ? ? ?// 3.創建數組對象
? ? ? ? ?ArrayClass[] arrayClass = new ArrayClass[10];
? ? ? ? ?// 輸出:無
? ? ? ? ?// 4. 調用class對象
? ? ? ? ?System.out.println(ArrayClass.class);
? ? ? ? ?// 輸出:無
? ? ? ? ?
? ? }
?}
?class SuperClass {
? ? ?public static int a = 0;
? ? ?static {
? ? ? ? ?System.out.println("super init");
? ? }
?}
??
?class SubClass extends SuperClass {
? ? ?public static final String str = "hello";
? ? ?public static final SuperClass superClass = new SuperClass();
? ? ?static {
? ? ? ? ?System.out.println("sub init");
? ? }
?}
??
?class ArrayClass {
? ? ?static {
? ? ? ? ?System.out.println("array init");
? ? }
?}
??
~~~
## 類加載器
作用:加載.class文件
類加載器的分類,級別從上到下:
* 啟動類(根)加載器:Bootstrap ClassLoader,加載Java的核心庫`jre/lib/rt.jar`和下面兩個類加載器,無法直接訪問。
* 擴展類加載器:Extension ClassLoader,加載Java的擴展庫`jre/ext/*.jar`。
* 應用程序加載器:Application ClassLoader,根據Java應用的類路徑Classpath加載自定義的Java類。
* 自定義類加載器。
:-: 
~~~
?@Test
?public void test1() {
? ? ?Student student = new Student();
? ? ?Class<? extends Student> aClass = student.getClass();
??
? ? ?System.out.println("aClass = " + aClass); // aClass = class domain.Student
? ? ?// sun.misc.Launcher$AppClassLoader@18b4aac2
? ? ?System.out.println(aClass.getClassLoader());
? ? ?// sun.misc.Launcher$ExtClassLoader@2ff4acd0
? ? ?System.out.println(aClass.getClassLoader().getParent());
? ? ?// null java調用不到,底層用C或C++寫的
? ? ?System.out.println(aClass.getClassLoader().getParent().getParent());
?}
~~~
設置類加載路徑的的虛擬機參數
~~~
?-Xbootclasspath/a:.
~~~
\-Xbootclasspath:用來設置啟動類加載器的類加載路徑,.表示追加當前目錄。
/a:.表示將當前目錄追加至啟動類加載路徑(jre/lib/rt.jar)之后。(a表示追加append)
可以用這個方法替換掉一些核心類:
* java -Xbootclasspath: 新的啟動類加載器的加載路徑。
* java -Xbootclasspath/a:
* java -Xbootclasspath/p:
例如:
~~~
?public class TestLoad {
? ? public static void main(String[] args) throws ClassNotFoundException {
? ? ? ? Class<?> aClass = Class.forName("classloading.F");
? ? ? ? System.out.println(aClass.getClassLoader());
? ? }
?}
??
?public class F {
??
? ? static {
? ? ? ? System.out.println("加載了F類");
? ? }
??
?}
~~~
執行:
~~~
?D:\MyCodes\IdeaProjects\jvm\out\production\jvm>java -Xbootclasspath/a:. classloading.TestLoad
~~~
~~~
?結果;
?加載了F類
?null
~~~
null就表示該類是由啟動類加載器加載的。
## 雙親(上級)委派機制
類加載器層級:
應用程序類加載器->擴展類加載器->啟動類加載器。
加載請求轉移規則:
1. 判斷被加載的類是否已經被加載,如果是則結束,否則將加載任務委托給自己父親加載器。
2. 父親加載器在收到加載請求后,判斷類是否已經被加載過,如果是則結束,否則繼續委托給自己的父親加載器。
3. 一直向上委托,直到啟動類加載器(Bootstrap ClassLoader)。
類正式加載規則:
1. 啟動類加載會判斷是否能夠完成加載任務,如果能就直接加載,不能就將加載任務委派給子類加載器。
2. 子類加載器判斷是否能夠完成加載任務,如果能就直接加載,如果不能就繼續將加載任務委派給子類加載器。
3. 一直向下委派,直到Application ClassLoader,如果還不能加載則會拋出**ClassNotFoundException**。

好處:
1. 安全:保證Java核心類不被破壞。
2. 唯一性:保證同一個類不會被加載多次。
源碼:
~~~
?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.
? ? ? ? ? ? ? ? ? ? ?long t1 = System.nanoTime();
? ? ? ? ? ? ? ? ? ? ?c = findClass(name);
??
? ? ? ? ? ? ? ? ? ? ?// this is the defining class loader; record the stats
? ? ? ? ? ? ? ? ? ? ?sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
? ? ? ? ? ? ? ? ? ? ?sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
? ? ? ? ? ? ? ? ? ? ?sun.misc.PerfCounter.getFindClasses().increment();
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? ? ? ?if (resolve) {
? ? ? ? ? ? ? ? ?resolveClass(c);
? ? ? ? ? ? }
? ? ? ? ? ? ?return c;
? ? ? ? }
? ? }
~~~
- 第一章 Java基礎
- ThreadLocal
- Java異常體系
- Java集合框架
- List接口及其實現類
- Queue接口及其實現類
- Set接口及其實現類
- Map接口及其實現類
- JDK1.8新特性
- Lambda表達式
- 常用函數式接口
- stream流
- 面試
- 第二章 Java虛擬機
- 第一節、運行時數據區
- 第二節、垃圾回收
- 第三節、類加載機制
- 第四節、類文件與字節碼指令
- 第五節、語法糖
- 第六節、運行期優化
- 面試常見問題
- 第三章 并發編程
- 第一節、Java中的線程
- 第二節、Java中的鎖
- 第三節、線程池
- 第四節、并發工具類
- AQS
- 第四章 網絡編程
- WebSocket協議
- Netty
- Netty入門
- Netty-自定義協議
- 面試題
- IO
- 網絡IO模型
- 第五章 操作系統
- IO
- 文件系統的相關概念
- Java幾種文件讀寫方式性能對比
- Socket
- 內存管理
- 進程、線程、協程
- IO模型的演化過程
- 第六章 計算機網絡
- 第七章 消息隊列
- RabbitMQ
- 第八章 開發框架
- Spring
- Spring事務
- Spring MVC
- Spring Boot
- Mybatis
- Mybatis-Plus
- Shiro
- 第九章 數據庫
- Mysql
- Mysql中的索引
- Mysql中的鎖
- 面試常見問題
- Mysql中的日志
- InnoDB存儲引擎
- 事務
- Redis
- redis的數據類型
- redis數據結構
- Redis主從復制
- 哨兵模式
- 面試題
- Spring Boot整合Lettuce+Redisson實現布隆過濾器
- 集群
- Redis網絡IO模型
- 第十章 設計模式
- 設計模式-七大原則
- 設計模式-單例模式
- 設計模式-備忘錄模式
- 設計模式-原型模式
- 設計模式-責任鏈模式
- 設計模式-過濾模式
- 設計模式-觀察者模式
- 設計模式-工廠方法模式
- 設計模式-抽象工廠模式
- 設計模式-代理模式
- 第十一章 后端開發常用工具、庫
- Docker
- Docker安裝Mysql
- 第十二章 中間件
- ZooKeeper