懸賞任務系統原始碼+兼職平臺系統原始碼
類的執行過程
以一個main方法舉例:
類載入的具體流程為:
1.載入:把class位元組碼檔案通過類載入器載入到記憶體中
2.驗證:校驗位元組碼檔案是否符合jvm規範
3.準備:給靜態變數賦初始值
比如8種基本型別的初值,預設為0;引用型別的初值則為null;常量的初值即為程式碼中設定的值,final static tmp = 456, 那麼該階段tmp的初值就是456
4.解析:將符號引用轉為直接引用
符號引用。即一個字串,但是這個字串給出了一些能夠唯一性識別一個方法,一個變數,一個類的相關資訊。
直接引用。可以理解為一個記憶體地址,或者一個偏移量。比如類方法,類變數的直接引用是指向方法區的指標;而例項方法,例項變數的直接引用則是從例項的頭指標開始算起到這個例項變數位置的偏移量
舉個例子來說,現在呼叫方法hello(),這個方法的地址是1234567,那麼hello就是符號引用,1234567就是直接引用。
在解析階段,虛擬機器會把所有的類名,方法名,欄位名這些符號引用替換為具體的記憶體地址或偏移量,也就是直接引用。
如果在類載入期間解析的則為“靜態連結”,程式碼執行到相應程式碼行才解析的則為“動態連結”。
舉個例子:
點選檢視程式碼
public static void main(String[] args) {
Math math = new Math();
math.compute();
}
其中main方法是在類載入時放到記憶體中的,為靜態連結;math.compute()是執行到這一行的時候才載入的,為動態連結。
5.初始化:對類的靜態變數初始化為指定的值,執行靜態程式碼塊
如果初始化一個類的時候,其父類尚未初始化,則優先初始化其父類。
如果同時包含多個靜態變數和靜態程式碼塊,則按照自上而下的順序依次執行。
注意:java中的類載入為懶載入,一個jar包啟動的時候不會載入所有的類,他只有用到了才會載入。
舉個例子:
點選檢視程式碼
public class Main4 { static { System.out.println("*************load TestDynamicLoad************"); } public static void main(String[] args) { new A(); System.out.println("*************load test************"); B b = null; } } class A { static { System.out.println("*************load A************"); } public A() { System.out.println("*************initial A************"); } } class B { static { System.out.println("*************load B************"); } public B() { System.out.println("*************initial B************"); } }
執行結果:
點選檢視程式碼
*************load TestDynamicLoad************
*************load A************
*************initial A************
*************load test************
如果將main方法中的B b = null;
修改為B b = new B();
,執行結果會變為:
點選檢視程式碼
*************load TestDynamicLoad************
*************load A************
*************initial A************
*************load test************
*************load B************
*************initial B************
類載入器與雙親委派
類載入器
java中的類載入器有引導類載入器、拓展類載入器、應用程式類載入器與自定義類載入器;
1.引導類載入器:負責載入位於jre的lib目錄下的核心類庫;
2.拓展類載入器:負責載入位於jre的lib目錄下ext目錄下的jar包;
3.應用程式類載入器:負責載入ClassPath路徑下的類包(自己寫的類);
4.自定義類載入器:負責載入使用者自定義路徑下的類包。
雙親委派
雙親委派的具體流程如下圖,在載入某個類時,首先會從AppClassLoader中判斷是否載入過此類,如果沒有載入,則委託父類載入器去判斷;如果都沒載入過,則由父類先判斷是否可以載入,父類可以載入則由父類載入,否則交給子類去載入。
雙親委派機制簡單說就是優先由父類去載入,父類載入不了再由子類載入。
這樣的好處是:
1.沙箱安全:自己寫的類不會覆蓋核心庫中的同名類,例如自己寫個java.lang.String,然後你想篡改它的實現,由於BootstrapClassLoader中已經有同名類,因此不會被載入,從一定程度防止了危險程式碼的注入。
2.避免類的重複載入;
全盤負責委託機制
ClassLoader載入類用的是全盤負責委託機制,“全盤負責”是指當一個ClassLoder裝載一個類時,除非顯示的使用另外一個ClassLoder,該類所依賴及引用的類通常也由這個ClassLoder載入。
實現程式碼如下:
點選檢視程式碼
//ClassLoader的loadClass方法,裡面實現了雙親委派機制
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
// 檢查當前類載入器是否已經載入了該類
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();
//都會呼叫URLClassLoader的findClass方法在載入器的類路徑裡查詢並載入該類
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.lang.ClassLoader 類,該類有兩個核心方法,一個是loadClass(String, boolean),實現了雙親委派機制,還有一個方法是findClass,預設實現是空方法,一般來說我們自定義類載入器主要是重寫findClass方法,如果想打破雙親委派,則需要我們覆寫loadClass方法。
觀察上面雙親委派的程式碼可知,只要刪除if (parent != null) { c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name); }
即可打破雙親委派。
實現程式碼如下:
點選檢視程式碼
/**
* 重寫類載入方法,實現自己的載入邏輯,不委派給雙親載入
*
* @param name
* @param resolve
* @return
* @throws ClassNotFoundException
*/
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) {
// 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.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
}