1. 程式人生 > >JVM:類載入機制

JVM:類載入機制

前言

JAVA虛擬機器中的類載入機制非常重要,在關於JVM的面試中經常問到,因此今天整理一下相關的知識點,一方面為之後的面試做準備,另一個方法也是擴充套件一下知識面。

類的生命週期

關於類的生命週期,直接上圖看下。
在這裡插入圖片描述
類從載入進虛擬機器記憶體,到從虛擬機器解除安裝的整個生命過程包括 載入、驗證、準備、解析、初始化、使用、解除安裝。
載入、驗證、準備、初始化和解除安裝的順序是固定的,但是解析不一定,它可以出現在初始化之後,是為了支援執行時動態繫結【多型】。

類的初始化時機

1、new物件、呼叫靜態屬性(非final)、呼叫靜態方法
2、class.forName,通過反射載入類,這個類如果沒有載入過
3、當要初始化的類有父類,則先初始化父類
4、執行main方法的類
5、多型,動態繫結【這個還要研究下,不理解。。】

類載入全過程

載入

1、通過類的全限定路徑名獲取定義該類的二進位制位元組流
2、將這個二進位制位元組流表示的靜態儲存結構轉化成方法區的執行時資料結構
3、在記憶體中生成一個java.lang.Class物件作為方法區中該類的訪問入口

注意:class物件比較特殊,雖然稱之為物件,但是放在方法區內,作為各類的訪問入口

驗證

目的:確保class物件中包含的位元組流資訊符合當前虛擬機器的要求,並且不會危害虛擬機器的安全。
步驟:包含
檔案格式驗證:作用,確保位元組流符合Class檔案的規範

元資料驗證:
作用,確保符合java語言 的語法。
例如:是否有父類。是否繼承了不該繼承【final】的類。是否重寫了抽象類和接口裡的方法。是否覆蓋了父類的final欄位。是否出現方法引數一致,返回值不一致。

位元組碼驗證:
作用:驗證方法體中的語法錯誤

符號引用驗證:
作用:確保符合引用是找得到的。

準備

作用:為靜態變數賦予零值
假設
public static int i=1;
所謂賦予的零值是0,並不是1.

解析

作用:符合引用解析為直接引用

初始化

作用:
自上而下初始化靜態變數【賦值】、執行靜態塊語句

類載入器

類與類載入器

注意:
比較兩個類是否相等的前提是通過同一個類載入器載入。
即使是同一個class檔案,同一個虛擬機器,由兩個不同的類載入器載入,這兩個class物件也是不相等的。

JVM中三種載入器

1、啟動類載入器:載入 %JAVA_HOME%\lib下的類
2、擴充套件類載入器:載入 %JAVA_HOME%\lib\ext下類
3、應用載入器:載入classPath下的類

雙親委派機制

在這裡插入圖片描述
如圖層次模型即雙親委派機制:
簡單來說,雙親委派機制要求除了頂層的啟動類載入器之外,其他的類載入器必須要有父載入器。

雙親委派機制的工作流程

1、如果一個類載入器收到載入類的請求,首先不會自己去載入,而是向上請求父載入器去載入
2、每一層都是如此,如果有父載入器,就向上傳遞請求
3、如果父載入器載入不了,子載入器才會嘗試去載入。

為什麼要雙親委派機制?

保證同一個全限定路徑名的類位元組流,無論被哪個載入器載入,最終都由最上層的載入器載入,保證不會出現多個不同的 全限定路徑名 一致的class物件。
舉例:
java.lang.Object ,存放在rt.jar,無論哪個載入器去載入,最終都委派給頂層的啟動類載入器,保證一致。

相反,如果沒有雙親委派機制,自己在classPath下寫一個java.lang.Object,通過不同的類載入器去載入的時候,可能在記憶體中出現多個Object的class物件,導致混亂。

打破雙親委派機制

雙親委派模型很好的解決了各個類載入器載入基礎類的統一性問題。即越基礎的類由越上層的載入器進行載入。
若載入的基礎類中需要回呼叫戶程式碼,而這時頂層的類載入器無法識別這些使用者程式碼,怎麼辦呢?這時就需要破壞雙親委派模型了。
下面介紹兩個例子來講解破壞雙親委派模型的過程。

JNDI破壞雙親委派模型
JNDI是Java標準服務,它的程式碼由啟動類載入器去載入。但是JNDI需要回調獨立廠商實現的程式碼,而類載入器無法識別這些回撥程式碼(SPI)。
為了解決這個問題,引入了一個執行緒上下文類載入器。 可通過Thread.setContextClassLoader()設定。
利用執行緒上下文類載入器去載入所需要的SPI程式碼,即父類載入器請求子類載入器去完成類載入的過程,而破壞了雙親委派模型。

Spring破壞雙親委派模型
Spring要對使用者程式進行組織和管理,而使用者程式一般放在WEB-INF目錄下,由WebAppClassLoader類載入器載入,而Spring由Common類載入器或Shared類載入器載入。
那麼Spring是如何訪問WEB-INF下的使用者程式呢?
使用執行緒上下文類載入器。 Spring載入類所用的classLoader都是通過Thread.currentThread().getContextClassLoader()獲取的。當執行緒建立時會預設建立一個AppClassLoader類載入器(對應Tomcat中的WebAppclassLoader類載入器): setContextClassLoader(AppClassLoader)。
利用這個來載入使用者程式。即任何一個執行緒都可通過getContextClassLoader()獲取到WebAppclassLoader。

如果不想打破雙親委派模型,就重寫ClassLoader類中的findClass()方法即可,無法被父類載入器載入的類最終會通過這個方法被載入。而如果想打破雙親委派模型則需要重寫loadClass()方法。