java虛擬機器類載入過程記憶體情況底層原始碼分析及ClassLoader講解
讀書筆記加自我總結-----------------------------------------------
《瘋狂JAVAj講義》
《深入理解JAVA虛擬機器》第七章虛擬機器載入機制
《傳智播客Java底層公開課視訊》教學視訊
參考:
一、虛擬機器的類載入機制
載入 連線 初始化 ------------> 程式執行期完成
壞處: 好處:
效能開銷 靈活性大大增強
二、虛擬機器載入的過程如下
三、載入之後在記憶體中的樣子
假設現在有這樣一個類
public class Demo { public static void main(String[] args) { new Person(); } } class Person{ private int age; public Person() { System.out.println("this is Person Construct Method"); } } class Animal{ public static String name; }
它在經過載入之後大致在記憶體中就如下:(非官方圖,個人意淫)
至於new一個類之後在堆中記憶體怎麼存放,咱們下節再細說
四、過程5初始化
自動收集1.類變數賦值,2靜態語句塊 形成<clinit>方法,各語句的順序按照在類檔案中出現的順序
虛擬機器會保證一個類的<clinit>()方法在多執行緒環境被正確的加鎖、同步。
如果多執行緒同時初始化一個類,只會一個執行緒執行<clinit>()方法,其他執行緒阻塞等待。執行的執行緒執行結束後,其它執行緒喚醒之後不會再進入<clinit>()。同一個類載入器下,一個類只會初始化一次。
五、過程1載入:載入器
過程1由一個特定的元件進行完成:類載入器
除了載入階段(圖1的階段1)使用者應用程式可以通過自定義載入器參與,剩下的動作完全由虛擬機器主導控制
也就是說類載入器的工作就是“通過一個類的全類名來獲取描述此類的二進位制位元組流”
有三類載入器:參見我的上一篇部落格:
ClassLoader是一個抽象類
public abstract class ClassLoader
JVM中除根載入器之外的所有載入器都是ClassLoader子類的例項
個人認為它大致有兩類方法
1.靜態的工具方法
比如
可以通過ClassLoader.getSystemClassLoader();獲得AppClass Loader(系統類載入器)
@CallerSensitive
public static ClassLoader getSystemClassLoader()
getParent()獲取該載入器的父類載入器
@CallerSensitive
public final ClassLoader getParent()
2 用於載入類的方法
我們通常通過繼承這個類來實現我們的自定義類載入器
1)public的loadClass方法,對外提供的入口,進行載入類
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
是ClassLoader類的入口點
為什麼叫入口點:
比如我們自己寫的OurClassLoader類
OurClassLoader ourCL=new OurClassLoader ()
Class<?> clazz=ourCL.loadClass("OurClass");
從而載入類並得到它的Class物件2)剩下的基本上都是protected方法
通過覆寫這些protected方法定製我們的功能loadClass(String name,boolean resolve),注意這個是protected方法,不是外部直接使用的(不是你想呼叫就呼叫的),我們可以覆寫這個方法
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;
}
}
它會執行如下步驟
用findLoadedClass(String)來檢查是否已經載入類,如果已經載入則直接返回
在父類載入器上呼叫loadClass()方法。如果父類載入器為null,則使用根類載入器來載入
如果父類載入失敗,則呼叫findClass(String)方法查詢類
findClass(String name)
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
我們要覆寫這個方法來自定義自己的載入類的行為
在這個方法中我們應該呼叫defineClass方法來把位元組碼轉為Class物件
defineClass()
方法負責將位元組碼分析成執行時的資料結構,並檢驗有效性。 此方法是final型,我們也無法重寫。
六、附加:父類委託機制
通過上面的過程我們能看到loadClass的確採用了父類委託和緩衝機制
那麼問題來了:如果我們覆寫loadClass方法呢,成功避開父類委託和緩衝機制(為什麼這麼任性),那能載入一個系統中已經存在的類嗎?
可以,因為:
JVM是如何判定兩個 Java 類是相同的: A.類的全名 B.載入此類的類載入器
我們自定義的一個新的類載入器,則載入這樣一個類 <com.silvia.MyClass , MyClassLoader>
我們知道CLASSPATH下的類是由Application classloader 系統載入器 載入的,所以如果com.silvia.MyClass放在CLASSPATH下可以被載入
這樣就可以有兩個com.silvia.MyClass類啦
但是
能不能載入java.lang.Object這種類呢
不能。JVM裡面做了相關安全認證。從而防止不可靠的甚至惡意的程式碼代替父載入器的可靠程式碼。
所以說一般來說我們應該覆寫findClass方法就好了,不要繞過父類委託機制(不要覆寫loadClass)