導航:首頁 > 編程語言 > java類載入器原理

java類載入器原理

發布時間:2023-08-03 12:11:51

A. java 類載入機制有什麼用

AVA類載入機制詳解

「代碼編譯的結果從本地機器碼轉變為位元組碼,是存儲格式發展的一小步,卻是變成語言發展的一大步」,這句話出自《深入理解JAVA虛擬機》一書,後面關於jvm的系列文章主要都是參考這本書。

JAVA源碼編譯由三個過程組成:

1、源碼編譯機制。

2、類載入機制

3、類執行機制

我們這里主要介紹編譯和類載入這兩種機制。

一、源碼編譯

代碼編譯由JAVA源碼編譯器來完成。主要是將源碼編譯成位元組碼文件(class文件)。位元組碼文件格式主要分為兩部分:常量池和方法位元組碼。

二、類載入

類的生命周期是從被載入到虛擬機內存中開始,到卸載出內存結束。過程共有七個階段,其中到初始化之前的都是屬於類載入的部分

載入----驗證----准備----解析-----初始化----使用-----卸載

系統可能在第一次使用某個類時載入該類,也可能採用預載入機制來載入某個類,當運行某個java程序時,會啟動一個java虛擬機進程,兩次運行的java程序處於兩個不同的JVM進程中,兩個jvm之間並不會共享數據。

1、載入階段

這個流程中的載入是類載入機制中的一個階段,這兩個概念不要混淆,這個階段需要完成的事情有:

1)通過一個類的全限定名來獲取定義此類的二進制位元組流。

2)將這個位元組流所代表的靜態存儲結構轉化為方法區的運行時數據結構。

3)在java堆中生成一個代表這個類的Class對象,作為訪問方法區中這些數據的入口。

由於第一點沒有指明從哪裡獲取以及怎樣獲取類的二進制位元組流,所以這一塊區域留給我開發者很大的發揮空間。這個我在後面的類載入器中在進行介紹。

2、准備階段

這個階段正式為類變數(被static修飾的變數)分配內存並設置類變數初始值,這個內存分配是發生在方法區中。

1、注意這里並沒有對實例變數進行內存分配,實例變數將會在對象實例化時隨著對象一起分配在JAVA堆中。

2、這里設置的初始值,通常是指數據類型的零值。

private static int a = 3;

這個類變數a在准備階段後的值是0,將3賦值給變數a是發生在初始化階段。

3、初始化階段

初始化是類載入機制的最後一步,這個時候才正真開始執行類中定義的JAVA程序代碼。在前面准備階段,類變數已經賦過一次系統要求的初始值,在初始化階段最重要的事情就是對類變數進行初始化,關注的重點是父子類之間各類資源初始化的順序。

java類中對類變數指定初始值有兩種方式:1、聲明類變數時指定初始值;2、使用靜態初始化塊為類變數指定初始值。

初始化的時機

1)創建類實例的時候,分別有:1、使用new關鍵字創建實例;2、通過反射創建實例;3、通過反序列化方式創建實例。

new Test();
Class.forName(「com.mengdd.Test」);

2)調用某個類的類方法(靜態方法)

Test.doSomething();

3)訪問某個類或介面的類變數,或為該類變數賦值。

int b=Test.a;
Test.a=b;

4)初始化某個類的子類。當初始化子類的時候,該子類的所有父類都會被初始化。

5)直接使用java.exe命令來運行某個主類。

除了上面幾種方式會自動初始化一個類,其他訪問類的方式都稱不會觸發類的初始化,稱為被動引用。

1、子類引用父類的靜態變數,不會導致子類初始化。

執行結果:

MIGU

用final修飾某個類變數時,它的值在編譯時就已經確定好放入常量池了,所以在訪問該類變數時,等於直接從常量池中獲取,並沒有初始化該類。

初始化的步驟

1、如果該類還沒有載入和連接,則程序先載入該類並連接。

2、如果該類的直接父類沒有載入,則先初始化其直接父類。

3、如果類中有初始化語句,則系統依次執行這些初始化語句。

在第二個步驟中,如果直接父類又有直接父類,則系統會再次重復這三個步驟來初始化這個父類,依次類推,JVM最先初始化的總是java.lang.Object類。當程序主動使用任何一個類時,系統會保證該類以及所有的父類都會被初始化。

B. java工作原理

Java工作原理
由四方面組成:

(1)Java編程語言
(2)Java類文件格式
(3)Java虛擬機
(4)Java應用程序介面
當編輯並運行一個Java程序時,需要同時涉及到這四種方面。使用文字編輯軟體(例如記事本、寫字板、UltraEdit等)或集成開發環境(Eclipse、MyEclipse等)在Java源文件中定義不同的類 ,通過調用類(這些類實現了Java API)中的方法來訪問資源系統,把源文件編譯生成一種二進制中間碼,存儲在class文件中,然後再通過運行與操作系統平台環境相對應的Java虛擬機來運行class文件,執行編譯產生的位元組碼,調用class文件中實現的方法來滿足程序的Java API調用 。

C. java中類載入器是怎麼工作的

JVM將類載入過程分為三個步驟:裝載(Load),鏈接(Link)和初始化(Initialize)
鏈接又分為三個步驟,驗證、准備、解析
1) 裝載:查找並載入類的二進制數據;
2)鏈接:
驗證:確保被載入類的正確性;
准備:為類的靜態變數分配內存,並將其初始化為默認值;
解析:把類中的符號引用轉換為直接引用;
3)初始化:為類的靜態變數賦予正確的初始值;
那為什麼我要有驗證這一步驟呢?首先如果由編譯器生成的class文件,它肯定是符合JVM位元組碼格式的,但是萬一有高手自己寫一個class文件,讓JVM載入並運行,用於惡意用途,就不妙了,因此這個class文件要先過驗證這一關,不符合的話不會讓它繼續執行的,也是為了安全考慮吧。
准備階段和初始化階段看似有點牟盾,其實是不牟盾的,如果類中有語句:private static int a = 10,它的執行過程是這樣的,首先位元組碼文件被載入到內存後,先進行鏈接的驗證這一步驟,驗證通過後准備階段,給a分配內存,因為變數a是static的,所以此時a等於int類型的默認初始值0,即a=0,然後到解析(後面在說),到初始化這一步驟時,才把a的真正的值10賦給a,此時

D. java虛擬機工作原理

從宏觀上介紹一下Java虛擬機的工作原理。從最初編寫的Java源文件(.java文件)是如何一步步執行的,如下圖所示,首先Java源文件經過前端編譯器(javac或ECJ)將.java文件編譯為Java位元組碼文件,然後JRE載入Java位元組碼文件,載入系統分配給JVM的內存區,然後執行引擎解釋或編譯類文件,再由即時編譯器將位元組碼轉化為機器碼。主要介紹下圖中的類載入器和運行時數據區兩個部分。

E. java解釋器如何載入類

類載入次序:1、靜態代碼塊或者靜態方法->2、main方法調用到的方法
對象載入次序:1、靜態代碼塊或者靜態方法->2、非靜態代碼塊或者非靜態方法->3、對象的構造方法。
但是有一段代碼沒有辦法解釋。代碼忘了,過段時間丟上來
個人感覺應該好像不大對勁,我覺得應該是:
類裝載時,1、靜態代碼塊或者靜態方法被調用
然後是程序的運行,main調用到的方法會被執行,如果是新建一個對象,則
2、非靜態代碼塊或者非靜態方法->3、對象的構造方法順序執行。

===============================================

首先我們要分析類載入原理,java中默認有三種類載入器:引導類載入器,擴展類載入器,系統類載入器(也叫應用類載入器)引導類載入器負責載入jdk中的系統類,這種類載入器都是用c語言實現的,在java程序中沒有辦法獲得這個類載入器,對於java程序是一個概念而已,基本上不用考慮它的存在,像String,Integer這樣的類都是由引導類載入器載入器的.
擴展類載入器負責載入標准擴展類,一般使用java實現,這是一個真正的java類載入器,負責載入jre/lib/ext中的類,和普通的類載入器一樣,其實這個類載入器對我們來說也不是很重要,我們可以通過java程序獲得這個類載入器。
系統類載入器,載入第一個應用類的載入器(其實這個定義並不準確,下面你將會看到),也就是執行java MainClass 時載入MainClass的載入器,這個載入器使用java實現,使用的很廣泛,負責載入classpath中指定的類。

類載入器之間有一定的關系(父子關系),我們可以認為擴展類載入器的父載入器是引導類載入器(當然不這樣認為也是可以的,因為引導類載入器表現在java中就是一個null),不過系統類載入器的父載入器一定是擴展類載入器,類載入器在載入類的時候會先給父載入器一個機會,只有父載入器無法載入時才會自己去載入。

我們無法獲得引導類載入器,因為它是使用c實現的,而且使用引導類載入器載入的類通過getClassLoader方法返回的是null.所以無法直接操作引導類載入器,但是我們可以根據Class.getClassLoader方法是否為null判斷這個類是不是引導類載入器載入的,可以通過下面的方法獲得引導類載入器載入的類路徑(每個jar包或者文件夾對應了一個URL);
sun.misc.Launcher.getBootstrapClassPath().getURLs()
你可以直接在你的main函數中輸出就可以了
System.out.println(java.util.Arrays.asList(sun.misc.Launcher.getBootstrapClassPath().getURLs()).toString());
得到的結果是:
[file:/C:/Program%20Files/Java/j2re1.4.2_10/lib/rt.jar,
file:/C:/Program%20Files/Java/j2re1.4.2_10/lib/i18n.jar,
file:/C:/Program%20Files/Java/j2re1.4.2_10/lib/sunrsasign.jar,
file:/C:/Program%20Files/Java/j2re1.4.2_10/lib/jsse.jar,
file:/C:/Program%20Files/Java/j2re1.4.2_10/lib/jce.jar,
file:/C:/Program%20Files/Java/j2re1.4.2_10/lib/charsets.jar,
file:/C:/Program%20Files/Java/j2re1.4.2_10/classes]

其實我們是可以指定引導類載入器的類路徑的,java提供了一個-Xbootclasspath參數,不過這個參數不是標准參數。
java -Xbootclasspath: 運行時指定引導類載入器的載入路徑(jar文件或者目錄)
java -Xbootclasspath/p:和上面的相同,不過把這個路徑放到原來的路徑前面
java -Xbootclasspath/a:這個就是在原引導類路徑後面添加類路徑。
上面我們有提過載入第一個應用類未必就是系統載入器。
如果我把這個應用類的路徑放到引導類路徑中,它將會被引導類載入器載入,大致這樣
java -Xbootclasspath/a:myjar.jar MainClass
如果MainClass在myjar.jar中,那麼這個類將會被引導類載入器載入。
如果希望看詳情,使用-verbose參數,為了看的更清楚,使用重定向,大致為(windows下):
java -verbose -Xbootclasspath/a:myjar.jar MainClass -> C:\out.txt
通過這個參數我們可以實現自己的系統類,比如替換掉java.lang.Object的實現,自己可以擴展
一些方法,不過這樣做似乎沒有好處,因為那就不是標准了。

我們最關心的還是系統類載入器,一般都認為系統類載入器是載入應用程序第一個類的載入器,
也就是java MainClass命令中載入MainClass的類載入器,這種說法雖然不是很嚴謹,但基本上還是可以這樣認為的,因為我們很少會改變引導類載入器和擴展類載入器的默認行為。應該說系統類載入器負責載入classpath路徑中的而且沒有被擴展類載入器載入的類(當然也包括引導類載入器載入的)。如果classpath中有這個類,但是這個類也在擴展類載入器的類路徑,那麼系統類載入器將沒有機會載入它。
我們很少改變擴展類載入器的行為,所以一般你自己定義的類都是系統類載入器載入器的。

獲得系統類載入器非常簡單,假設MyClass是你定義的一個類
MyClass.class.getClassLoader()返回的就是系統類載入器,當然這種方法無法保證絕對正確,我們可以使用更簡單而且一定正確的方式:
ClassLoader.getSystemClassLoader()獲得系統類載入器。我們知道ClassLoader是一個抽象類,所以系統類載入器肯定是ClassLoader的一個子類實現。我們來看看它是什麼
ClassLoader.getSystemClassLoader().getClass();
結果是class sun.misc.Lancher$AppClassLoader
可以看出這是sun的一個實現,從名字可以看出是一個內部類,目前我也沒有看到這個源代碼,似乎還不是很清晰:
我們在看看它的父類是什麼:
ClassLoader.getSystemClassLoader().getClass().getSuperclass();
結果是:class java.net.URLClassLoader
這個是j2se的標准類,它的父類是SecureClassLoader,而SecureClassLoader是繼承ClassLoader的。
現在整個關系應該很清楚,我們會看到幾乎所有的ClassLoader實現都是繼承URLClassLoader的。
因為系統類載入器是非常重要的,而且是我們可以直接控制的,所以我們後面還會介紹,不過先來看一下擴展類
載入器以及它們之間的關系。

擴展類載入器似乎是一個不起眼的角色,它負責載入java的標准擴展(jre/lib/ext目錄下的所有jar),它其實就是一個普通的載入器,看得見摸得著的。
首先的問題是怎麼知道擴展類載入器在哪裡?
的確沒有直接途徑獲得擴展類載入器,但是我們知道它是系統類載入器的父載入器,我們已經很容易的獲得系統類載入器了,所以我們可以間接的獲得擴展類載入器:
ClassLoader.getSystemClassLoader().getParent().getClass();
其實是通過系統類載入器間接的獲得了擴展類載入器,看看是什麼東西:
結果是:class sun.misc.Launcher$ExtClassLoader
這個類和系統類載入器一樣是一個內部類,而且定義在同一個類中。
同樣看看它的父類是什麼:
ClassLoader.getSystemClassLoader().getParent().getClass().getSuperclass();
可以看出結果也是class java.net.URLClassLoader
擴展類載入jre/lib/ext目錄下的所有類,包括jar,目錄下的所有類(目錄名不一定要classes).
現在可以回答上面的問題了,你寫一個HelloWorld,放到jre/lib/ext/下的某個目錄
比如 jre/lib/ext/myclass/HelloWorld.class
然後在你classpath也設置一份到這個類的路徑,結果執行java HelloWorld時,這個類是被擴展類載入器載入器的,可以這樣證明
public static void main(String[] args){
System.out.println("loaded by"+HelloWorld.class.getClassLoader().getClass());
System.out.println("Hello World");
}
結果可以得到class sun.misc.Launcher$ExtClassLoader
當然如果你把jre/lib/ext下myclass這個目錄刪除,仍然可以運行,但是這樣結果是
class sun.misc.Lancher$AppClassLoader
如果你不知道這個過程的話,假設在你擴展類路徑下有一份classpath中的拷貝,或者是比較低的版本,當你使用新的版本時會發現沒有起作用,知道這個過程你就不會覺得奇怪了。另外就是兩個不同的類載入器是可以載入一個同名的類的,也就是說雖然擴展類載入器載入了某個類,系統類載入器是可以載入自己的版本的,
但是現有的實現都沒有這樣做,ClassLoader中的方法是會請求父類載入器先載入的,如果你自己定義類載入器完全可以修改這種默認行為,甚至可以讓他沒有父載入器。

這里給出一個方法如何獲得擴展類載入器載入的路徑:
String path=System.getProperty("java.ext.dirs");
File dir=new File(path);
if(!dir.exists()||!dir.isDirectory()){
return Collections.EMPTY_LIST;
}
File[] jars=dir.listFiles();
URL[] urls=new URL[jars.length];
for(int i=0;i<jars.length;i++){
urls[i]=sun.misc.URLClassPath.pathToURLs(jars[i].getAbsolutePath())[0];
}
return Arrays.asList(urls);

對於擴展類載入器我們基本上不會去關心,也很少把你自己的jar放到擴展路徑,大部分情況下我們都感覺不到它的存在,當然如果你一定要放到這個目錄下,一定要知道這個過程,它會優先於classpath中的類。

現在我們應該很清楚知道某個類是哪個載入器載入的,並且知道為什麼是它載入的,如果要在運行時獲得某個類的類載入器,直接使用Class的getClassLoader()方法就可以了。

用戶定義的類一般都是系統類載入器載入的,我們很少直接使用類載入器載入類,我們甚至很少自己載入類。
因為類在使用時會被自動載入,我們用到某個類時該類會被自動載入,比如new A()會導致類A自動被載入,不過這種載入只發生一次。
我們也可以使用系統類載入器手動載入類,ClassLoader提供了這個介面
ClassLoader.getSystemClassLoader().loadClass("classFullName");
這就很明確的指定了使用系統類載入器載入指定的類,但是如果該類能夠被擴展類載入器載入,系統類載入器還是不會有機會的。
我們最常用的還是使用Class.forName載入使用的類,這種方式沒有指定某個特定的ClassLoader,會使用調用類的ClassLoader。
也就是說調用這個方法的類的類載入器將會用於載入這個類。比如在類A中使用Class.forName載入類B,那麼載入類A的類載入器將會用於載入類B,這樣兩個類的類載入器是同一個。

最後討論一下如何獲得某個類載入器載入了哪些類,這個似乎有一定的使用價值,可以看出哪些類被載入了。其實這個也不是很難,因為ClassLoader中有一個classes成員變數就是用來保存類載入器載入的類列表,而且有一個方法
void addClass(Class c) { classes.addElement(c);}
這個方法被JVM調用。
我們只要利用反射獲得classes這個值就可以了,不過classes聲明為private的,我們需要修改它的訪問許可權(沒有安全管理器時很容易做到)
classes = ClassLoader.class.getDeclaredField("classes");
classes.setAccessible(true);
List ret=(List) classes.get(cl); //classes是一個Vector
可惜的是對於引導類載入器沒有辦法獲得載入的類,因為它是c實現的,在java中很難控制

閱讀全文

與java類載入器原理相關的資料

熱點內容
常微分第三版答案pdf 瀏覽:260
安卓機qq怎麼開懸浮窗 瀏覽:59
備孕三年了怎麼緩解壓力大 瀏覽:739
登錄阿里雲伺服器黑屏 瀏覽:334
匿名元組Python 瀏覽:723
華為運動健康存儲的文件夾 瀏覽:520
cad正多邊形命令 瀏覽:461
壓縮比150能加97油嗎 瀏覽:397
linux新建群在哪個文件夾 瀏覽:794
韻達快運app在哪裡簽收 瀏覽:336
阿里雲伺服器如何綁定備案域名 瀏覽:273
單片機用什麼鍵盤好 瀏覽:25
android動畫面試 瀏覽:309
pdf無法刪除 瀏覽:90
ftp刪除文件java 瀏覽:89
裂變棋牌源碼 瀏覽:87
邀月命令江別鶴退婚 瀏覽:656
春秋app在哪裡查詢 瀏覽:136
用linux設計 瀏覽:747
安卓intent什麼意思 瀏覽:946