描述一下JVM加载class文件的原理机制
?
?
Java中的所有類,都需要由類加載器裝載到JVM中才能運(yùn)行。類加載器本身也是一個(gè)類,而它的工作就是把class文件從硬盤讀取到內(nèi)存中。在寫程序的時(shí)候,我們幾乎不需要關(guān)心類的加載,因?yàn)檫@些都是隱式裝載的,除非我們有特殊的用法,像是反射,就需要顯式的加載所需要的類。
?
類裝載方式,有兩種 :
1.隱式裝載, 程序在運(yùn)行過(guò)程中當(dāng)碰到通過(guò)new 等方式生成對(duì)象時(shí),隱式調(diào)用類裝載器加載對(duì)應(yīng)的類到j(luò)vm中,
2.顯式裝載, 通過(guò)class.forname()等方法,顯式加載需要的類
?
Java類的加載是動(dòng)態(tài)的,它并不會(huì)一次性將所有類全部加載后再運(yùn)行,而是保證程序運(yùn)行的基礎(chǔ)類(像是基類)完全加載到j(luò)vm中,至于其他類,則在需要的時(shí)候才加載。這當(dāng)然就是為了節(jié)省內(nèi)存開(kāi)銷。
?
Java的類加載器有三個(gè),對(duì)應(yīng)Java的三種類:
?
Bootstrap Loader ?:啟動(dòng)類加載器,是虛擬機(jī)自身的一部分。負(fù)責(zé)將存放在\lib目錄中的類庫(kù)加載到虛擬機(jī)中。其無(wú)法被Java程序直接引用。?負(fù)責(zé)加載系統(tǒng)類 (指的是內(nèi)置類,像是String,對(duì)應(yīng)于C#中的System類和C/C++標(biāo)準(zhǔn)庫(kù)中的類)
?????????????
ExtClassLoader ??:?負(fù)責(zé)加載擴(kuò)展類(就是繼承類和實(shí)現(xiàn)類)
???????????????????????????
AppClassLoader ??:負(fù)責(zé)加載用戶類路徑(ClassPath)上所指定的類庫(kù)(程序員自定義的類)
?
JVM中類的加載是由類加載器(ClassLoader)和它的子類來(lái)實(shí)現(xiàn)的,Java中的類加載器是一個(gè)重要的Java運(yùn)行時(shí)系統(tǒng)組件,它負(fù)責(zé)在運(yùn)行時(shí)查找和裝入類文件中的類。
由于Java的跨平臺(tái)性,經(jīng)過(guò)編譯的Java源程序并不是一個(gè)可執(zhí)行程序,而是一個(gè)或多個(gè)類文件。當(dāng)Java程序需要使用某個(gè)類時(shí),JVM會(huì)確保這個(gè)類已經(jīng)被加載、連接(驗(yàn)證、準(zhǔn)備和解析)和初始化。
類的加載是指把類的.class文件中的數(shù)據(jù)讀入到內(nèi)存中,通常是創(chuàng)建一個(gè)字節(jié)數(shù)組讀入.class文件,然后產(chǎn)生與所加載類對(duì)應(yīng)的Class對(duì)象。加載完成后,Class對(duì)象還不完整,所以此時(shí)的類還不可用。
當(dāng)類被加載后就進(jìn)入連接階段,這一階段包括
驗(yàn)證:為了確保Class文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)的要求,并且不會(huì)危害虛擬機(jī)自身的安全。
準(zhǔn)備:為靜態(tài)變量分配內(nèi)存并設(shè)置默認(rèn)的初始值。
解析:將符號(hào)引用替換為直接引用。
最后JVM對(duì)類進(jìn)行初始化,包括:1)如果類存在直接的父類并且這個(gè)類還沒(méi)有被初始化,那么就先初始化父類;2)如果類中存在初始化語(yǔ)句,就依次執(zhí)行這些初始化語(yǔ)句。
類的加載是由類加載器完成的,類加載器包括:啟動(dòng)類加載器(BootStrap)、擴(kuò)展類加載器(Extension)、應(yīng)用程序類加載器(Application)。
從Java 2(JDK 1.2)開(kāi)始,類加載過(guò)程采取了雙親委派模型(PDM)。PDM更好的保證了Java平臺(tái)的安全性,在該機(jī)制中,JVM自帶的Bootstrap是啟動(dòng)類加載器,其他的加載器都有且僅有一個(gè)父類加載器。類的加載首先請(qǐng)求父類加載器加載,父類加載器無(wú)能為力時(shí)才由其子類加載器自行加載。JVM不會(huì)向Java程序提供對(duì)Bootstrap的引用。
?
雙親委派模型:要求除了頂層的啟動(dòng)類加載器外,其余加載器都應(yīng)當(dāng)有自己的父類加載器。類加載器之間的父子關(guān)系,一般不會(huì)以繼承的關(guān)系來(lái)實(shí)現(xiàn),而是通過(guò)組合關(guān)系復(fù)用父加載器的代碼。
工作過(guò)程:如果一個(gè)類加載器收到了類加載的請(qǐng)求,它首先不會(huì)自己去嘗試加載這個(gè)類,而是把這個(gè)請(qǐng)求委派給父類加載器完成。
每個(gè)層次的類加載器都是如此,因此所有的加載請(qǐng)求最終都應(yīng)該傳送到頂層的啟動(dòng)類加載器中,
只有到父加載器反饋?zhàn)约簾o(wú)法完成這個(gè)加載請(qǐng)求(它的搜索范圍沒(méi)有找到所需的類)時(shí),子加載器才會(huì)嘗試自己去加載。
為什么要使用:Java類隨著它的類加載器一起具備了一種帶優(yōu)先級(jí)的層次關(guān)系。
比如java.lang.Object,它存放在rt.jar中,無(wú)論哪個(gè)類加載器要加載這個(gè)類,最終都是委派給啟動(dòng)類加載器進(jìn)行加載,
因此Object類在程序的各個(gè)類加載器環(huán)境中,都是同一個(gè)類。
自己編寫一個(gè)與rt.jar類庫(kù)中已有類重名的java類,可以正常編譯,但無(wú)法被加載運(yùn)行。
?
委托機(jī)制的意義 — 防止內(nèi)存中出現(xiàn)多份同樣的字節(jié)碼?
比如兩個(gè)類A和類B都要加載System類:
如果不用委托而是自己加載自己的,那么類A就會(huì)加載一份System字節(jié)碼,然后類B又會(huì)加載一份System字節(jié)碼,這樣內(nèi)存中就出現(xiàn)了兩份System字節(jié)碼。
如果使用委托機(jī)制,會(huì)遞歸的向父類查找,也就是首選用Bootstrap嘗試加載,如果找不到再向下。這里的System就能在Bootstrap中找到然后加載,如果此時(shí)類B也要加載System,也從Bootstrap開(kāi)始,此時(shí)Bootstrap發(fā)現(xiàn)已經(jīng)加載過(guò)了System那么直接返回內(nèi)存中的System即可而不需要重新加載,這樣內(nèi)存中就只有一份System的字節(jié)碼了。
?
能不能自己寫個(gè)類叫java.lang.System?
答案:通常不可以,但可以采取另類方法達(dá)到這個(gè)需求。?
解釋:為了不讓我們寫System類,類加載采用委托機(jī)制,這樣可以保證父類加載器優(yōu)先,父類加載器能找到的類,子加載器就沒(méi)有機(jī)會(huì)加載。而System類是Bootstrap加載器加載的,就算自己重寫,也總是使用Java系統(tǒng)提供的System,自己寫的System類根本沒(méi)有機(jī)會(huì)得到加載。
但是,我們可以自己定義一個(gè)類加載器來(lái)達(dá)到這個(gè)目的,為了避免雙親委托機(jī)制,這個(gè)類加載器也必須是特殊的。由于系統(tǒng)自帶的三個(gè)類加載器都加載特定目錄下的類,如果我們自己的類加載器放在一個(gè)特殊的目錄,那么系統(tǒng)的加載器就無(wú)法加載,也就是最終還是由我們自己的加載器加載。
?
?
?
1、JVM?簡(jiǎn)介
JVM?是我們Javaer?的最基本功底了,剛開(kāi)始學(xué)Java?的時(shí)候,一般都是從“Hello World?”開(kāi)始的,然后會(huì)寫個(gè)復(fù)雜點(diǎn)class?,然后再找一些開(kāi)源框架,比如Spring?,Hibernate?等等,再然后就開(kāi)發(fā)企業(yè)級(jí)的應(yīng)用,比如網(wǎng)站、企業(yè)內(nèi)部應(yīng)用、實(shí)時(shí)交易系統(tǒng)等等,直到某一天突然發(fā)現(xiàn)做的系統(tǒng)咋就這么慢呢,而且時(shí)不時(shí)還來(lái)個(gè)內(nèi)存溢出什么的,今天是交易系統(tǒng)報(bào)了StackOverflowError?,明天是網(wǎng)站系統(tǒng)報(bào)了個(gè)OutOfMemoryError?,這種錯(cuò)誤又很難重現(xiàn),只有分析Javacore?和dump?文件,運(yùn)氣好點(diǎn)還能分析出個(gè)結(jié)果,運(yùn)行遭的點(diǎn),就直接去廟里燒香吧!每天接客戶的電話都是戰(zhàn)戰(zhàn)兢兢的,生怕再出什么幺蛾子了。我想Java?做的久一點(diǎn)的都有這樣的經(jīng)歷,那這些問(wèn)題的最終根結(jié)是在哪呢?——?JVM?。
JVM?全稱是Java Virtual Machine?,Java?虛擬機(jī),也就是在計(jì)算機(jī)上再虛擬一個(gè)計(jì)算機(jī),這和我們使用?VMWare不一樣,那個(gè)虛擬的東西你是可以看到的,這個(gè)JVM?你是看不到的,它存在內(nèi)存中。我們知道計(jì)算機(jī)的基本構(gòu)成是:運(yùn)算器、控制器、存儲(chǔ)器、輸入和輸出設(shè)備,那這個(gè)JVM?也是有這成套的元素,運(yùn)算器是當(dāng)然是交給硬件CPU?還處理了,只是為了適應(yīng)“一次編譯,隨處運(yùn)行”的情況,需要做一個(gè)翻譯動(dòng)作,于是就用了JVM?自己的命令集,這與匯編的命令集有點(diǎn)類似,每一種匯編命令集針對(duì)一個(gè)系列的CPU?,比如8086?系列的匯編也是可以用在8088?上的,但是就不能跑在8051?上,而JVM?的命令集則是可以到處運(yùn)行的,因?yàn)镴VM?做了翻譯,根據(jù)不同的CPU?,翻譯成不同的機(jī)器語(yǔ)言。
JVM?中我們最需要深入理解的就是它的存儲(chǔ)部分,存儲(chǔ)?硬盤?NO?,NO?,?JVM?是一個(gè)內(nèi)存中的虛擬機(jī),那它的存儲(chǔ)就是內(nèi)存了,我們寫的所有類、常量、變量、方法都在內(nèi)存中,這決定著我們程序運(yùn)行的是否健壯、是否高效,接下來(lái)的部分就是重點(diǎn)介紹之。
回到頂部2、JVM?的組成部分
我們先把JVM?這個(gè)虛擬機(jī)畫出來(lái),如下圖所示:
?
從這個(gè)圖中可以看到,JVM?是運(yùn)行在操作系統(tǒng)之上的,它與硬件沒(méi)有直接的交互。我們?cè)賮?lái)看下JVM?有哪些組成部分,如下圖所示:
該圖參考了網(wǎng)上廣為流傳的JVM?構(gòu)成圖,大家看這個(gè)圖,整個(gè)JVM?分為四部分:
## Class Loader?類加載器?
類加載器的作用是加載類文件到內(nèi)存,比如編寫一個(gè)HelloWord.java?程序,然后通過(guò)javac?編譯成class?文件,那怎么才能加載到內(nèi)存中被執(zhí)行呢?Class Loader?承擔(dān)的就是這個(gè)責(zé)任,那不可能隨便建立一個(gè).class?文件就能被加載的,Class Loader?加載的class?文件是有格式要求,在《JVM Specification?》中式這樣定義Class?文件的結(jié)構(gòu):
ClassFile {u4 magic;u2 minor_version;u2 major_version;u2 constant_pool_count;cp_info constant_pool[constant_pool_count-1];u2 access_flags;u2 this_class;u2 super_class;u2 interfaces_count;u2 interfaces[interfaces_count];u2 fields_count;field_info fields[fields_count];u2 methods_count;method_info methods[methods_count];u2 attributes_count;attribute_info attributes[attributes_count];}?
需要詳細(xì)了解的話,可以仔細(xì)閱讀《JVM Specification?》的第四章“The class File Format?”,這里不再詳細(xì)說(shuō)明。
友情提示:Class Loader?只管加載,只要符合文件結(jié)構(gòu)就加載,至于說(shuō)能不能運(yùn)行,則不是它負(fù)責(zé)的,那是由Execution Engine?負(fù)責(zé)的。
##?Execution Engine?執(zhí)行引擎?
執(zhí)行引擎也叫做解釋器(Interpreter)?,負(fù)責(zé)解釋命令,提交操作系統(tǒng)執(zhí)行。
##?Native Interface?本地接口
本地接口的作用是融合不同的編程語(yǔ)言為Java?所用,它的初衷是融合C/C++?程序,Java?誕生的時(shí)候是C/C++?橫行的時(shí)候,要想立足,必須有一個(gè)聰明的、睿智的調(diào)用C/C++?程序,于是就在內(nèi)存中專門開(kāi)辟了一塊區(qū)域處理標(biāo)記為native?的代碼,它的具體做法是Native Method Stack?中登記native?方法,在Execution Engine?執(zhí)行時(shí)加載native libraies?。目前該方法使用的是越來(lái)越少了,除非是與硬件有關(guān)的應(yīng)用,比如通過(guò)Java?程序驅(qū)動(dòng)打印機(jī),或者Java?系統(tǒng)管理生產(chǎn)設(shè)備,在企業(yè)級(jí)應(yīng)用中已經(jīng)比較少見(jiàn),因?yàn)楝F(xiàn)在的異構(gòu)領(lǐng)域間的通信很發(fā)達(dá),比如可以使用Socket?通信,也可以使用Web Service?等等,不多做介紹。
## Runtime data area?運(yùn)行數(shù)據(jù)區(qū)?
運(yùn)行數(shù)據(jù)區(qū)是整個(gè)JVM?的重點(diǎn)。我們所有寫的程序都被加載到這里,之后才開(kāi)始運(yùn)行,Java?生態(tài)系統(tǒng)如此的繁榮,得益于該區(qū)域的優(yōu)良自治。
?
整個(gè)JVM?框架由加載器加載文件,然后執(zhí)行器在內(nèi)存中處理數(shù)據(jù),需要與異構(gòu)系統(tǒng)交互是可以通過(guò)本地接口進(jìn)行,瞧,一個(gè)完整的系統(tǒng)誕生了!
回到頂部3、JVM加載class文件的原理機(jī)制?
?Java中的所有類,都需要由類加載器裝載到JVM中才能運(yùn)行。類加載器本身也是一個(gè)類,而它的工作就是把class文件從硬盤讀取到內(nèi)存中。在寫程序的時(shí)候,我們幾乎不需要關(guān)心類的加載,因?yàn)檫@些都是隱式裝載的,除非我們有特殊的用法,像是反射,就需要顯式的加載所需要的類。
類裝載方式,有兩種?
??? 1.隱式裝載, 程序在運(yùn)行過(guò)程中當(dāng)碰到通過(guò)new 等方式生成對(duì)象時(shí),隱式調(diào)用類裝載器加載對(duì)應(yīng)的類到j(luò)vm中,
??? 2.顯式裝載, 通過(guò)class.forname()等方法,顯式加載需要的類?
? 隱式加載與顯式加載的區(qū)別:兩者本質(zhì)是一樣??
? ? ?Java類的加載是動(dòng)態(tài)的,它并不會(huì)一次性將所有類全部加載后再運(yùn)行,而是保證程序運(yùn)行的基礎(chǔ)類(像是基類)完全加載到j(luò)vm中,至于其他類,則在需要的時(shí)候才加載。這當(dāng)然就是為了節(jié)省內(nèi)存開(kāi)銷。
? Java的類加載器有三個(gè),對(duì)應(yīng)Java的三種類:(java中的類大致分為三種:?? 1.系統(tǒng)類?? 2.擴(kuò)展類?3.由程序員自定義的類?)
???? Bootstrap Loader? // 負(fù)責(zé)加載系統(tǒng)類?(指的是內(nèi)置類,像是String,對(duì)應(yīng)于C#中的System類和C/C++標(biāo)準(zhǔn)庫(kù)中的類)
??????????? |?
????????? - - ExtClassLoader?? //?負(fù)責(zé)加載擴(kuò)展類(就是繼承類和實(shí)現(xiàn)類)
????????????????????????? |?
????????????????????? - - AppClassLoader?? //?負(fù)責(zé)加載應(yīng)用類(程序員自定義的類)
?三個(gè)加載器各自完成自己的工作,但它們是如何協(xié)調(diào)工作呢?哪一個(gè)類該由哪個(gè)類加載器完成呢?為了解決這個(gè)問(wèn)題,Java采用了委托模型機(jī)制。
委托模型機(jī)制的工作原理很簡(jiǎn)單:當(dāng)類加載器需要加載類的時(shí)候,先請(qǐng)示其Parent(即上一層加載器)在其搜索路徑載入,如果找不到,才在自己的搜索路徑搜索該類。這樣的順序其實(shí)就是加載器層次上自頂而下的搜索,因?yàn)榧虞d器必須保證基礎(chǔ)類的加載。之所以是這種機(jī)制,還有一個(gè)安全上的考慮:如果某人將一個(gè)惡意的基礎(chǔ)類加載到j(luò)vm,委托模型機(jī)制會(huì)搜索其父類加載器,顯然是不可能找到的,自然就不會(huì)將該類加載進(jìn)來(lái)。
????? 我們可以通過(guò)這樣的代碼來(lái)獲取類加載器:
ClassLoader loader = ClassName.class.getClassLoader(); ClassLoader ParentLoader = loader.getParent();?
注意一個(gè)很重要的問(wèn)題,就是Java在邏輯上并不存在BootstrapKLoader的實(shí)體!因?yàn)樗怯肅++編寫的,所以打印其內(nèi)容將會(huì)得到null。
??????
前面是對(duì)類加載器的簡(jiǎn)單介紹,它的原理機(jī)制非常簡(jiǎn)單,就是下面幾個(gè)步驟:
1.裝載:查找和導(dǎo)入class文件;
2.連接:
??????(1)檢查:檢查載入的class文件數(shù)據(jù)的正確性;
??????(2)準(zhǔn)備:為類的靜態(tài)變量分配存儲(chǔ)空間;
??????(3)解析:將符號(hào)引用轉(zhuǎn)換成直接引用(這一步是可選的)
3.初始化:初始化靜態(tài)變量,靜態(tài)代碼塊。
??????這樣的過(guò)程在程序調(diào)用類的靜態(tài)成員的時(shí)候開(kāi)始執(zhí)行,所以靜態(tài)方法main()才會(huì)成為一般程序的入口方法。類的構(gòu)造器也會(huì)引發(fā)該動(dòng)作。
-------------我是低調(diào)的分割線--------------------------?
?附錄:
https://www.cnblogs.com/Qian123/p/5707562.html
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
轉(zhuǎn)載于:https://www.cnblogs.com/williamjie/p/11167920.html
總結(jié)
以上是生活随笔為你收集整理的描述一下JVM加载class文件的原理机制的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 华南x79设置u盘启动(华南x79u盘启
- 下一篇: 被誉为天下第一山是哪座山(中国安徽奇险天