JVM類結(jié)構(gòu)類加載類執(zhí)行
類加載的五個(gè)過程:加載、驗(yàn)證枫笛、準(zhǔn)備、解析往枣、初始化进胯。
- 加載: 根據(jù)全限定名來獲取定義類的二進(jìn)制字節(jié)流,然后將該字節(jié)流所代表的靜態(tài)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu),最后在生成一個(gè)代表該類的Class對(duì)象,作為方法區(qū)這些數(shù)據(jù)的訪問入口.
- 驗(yàn)證:主要時(shí)為了確保class文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)的要求,并且不會(huì)危害虛擬機(jī)自身的安全.包含四個(gè)階段的驗(yàn)證過程:
- 文件格式驗(yàn)證:保證輸入的字節(jié)流能夠正確地解析并存儲(chǔ)在方法區(qū)之內(nèi),格式上符合描述一個(gè)java類型信息的要求
- 元數(shù)據(jù)驗(yàn)證:字節(jié)碼語(yǔ)義信息的驗(yàn)證,以保證描述的信息符合java語(yǔ)言規(guī)范.驗(yàn)證點(diǎn)有:這個(gè)類是否有父類等.
- 字節(jié)碼驗(yàn)證:主要是進(jìn)行數(shù)據(jù)流和控制流分析,保證被校驗(yàn)類的方法在運(yùn)行時(shí)不會(huì)做出危害虛擬機(jī)安全的行為.
- 符號(hào)引用驗(yàn)證:對(duì)符號(hào)引用轉(zhuǎn)化為直接引用過程的驗(yàn)證.
- 準(zhǔn)備:為類變量分配內(nèi)存并設(shè)置變量的初始值, 這些內(nèi)存在方法區(qū)進(jìn)行分配.
- 解析:將虛擬機(jī)常量池中的符號(hào)引用轉(zhuǎn)化為直接引用的過程.解析主要是針對(duì)類或接口、字段原押、類方法胁镐、類接口方法四類.
- 初始化:執(zhí)行靜態(tài)變量的賦值操作以及靜態(tài)代碼塊,完成初識(shí)化.初始化過程保證了父類中定義的初始化優(yōu)先于子類的初始化.但接口不需要執(zhí)行父類的初始化.
類初始化階段解析
https://blog.csdn.net/ns_code/article/details/17881581
類變量初始化順序
https://blog.csdn.net/jianggujin/article/details/52228983
類class文件解析
https://blog.csdn.net/ns_code/article/details/17675609
JVM類加載時(shí)機(jī)
- 何時(shí)開始類的初始化
什么情況下需要開始類加載過程的第一個(gè)階段:"加載"。虛擬機(jī)規(guī)范中并沒強(qiáng)行約束诸衔,這點(diǎn)可以交給虛擬機(jī)的的具體實(shí)現(xiàn)自由把握盯漂,但是對(duì)于初始化階段虛擬機(jī)規(guī)范是嚴(yán)格規(guī)定了如下幾種情況,如果類未初始化會(huì)對(duì)類進(jìn)行初始化笨农。
- 創(chuàng)建類的實(shí)例
- 訪問類的靜態(tài)變量(除常量【被final修辭的靜態(tài)變量】原因:常量一種特殊的變量就缆,因?yàn)榫幾g器把他們當(dāng)作值(value)而不是域(field)來對(duì)待。如果你的代碼中用到了常變量(constant variable)谒亦,編譯器并不會(huì)生成字節(jié)碼來從對(duì)象中載入域的值竭宰,而是直接把這個(gè)值插入到字節(jié)碼中空郊。這是一種很有用的優(yōu)化,但是如果你需要改變final域的值那么每一塊用到那個(gè)域的代碼都需要重新編譯切揭。
- 訪問類的靜態(tài)方法
- 反射如(Class.forName("my.xyz.Test"))
- 當(dāng)初始化一個(gè)類時(shí)狞甚,發(fā)現(xiàn)其父類還未初始化,則先出發(fā)父類的初始化
- 虛擬機(jī)啟動(dòng)時(shí)廓旬,定義了main()方法的那個(gè)類先初始化
以上情況稱為稱對(duì)一個(gè)類進(jìn)行“主動(dòng)引用”哼审,除此種情況之外,均有且僅有這幾種情況不會(huì)觸發(fā)類的初始化孕豹,稱為“被動(dòng)引用”
接口的加載過程與類的加載過程稍有不同涩盾。接口中不能使用static{}塊。當(dāng)一個(gè)接口在初始化時(shí)励背,并不要求其父接口全部都完成了初始化春霍,只有真正在使用到父接口時(shí)(例如引用接口中定義的常量)才會(huì)初始化。
- 被動(dòng)引用例子
- 子類調(diào)用父類的靜態(tài)變量椅野,子類不會(huì)被初始化终畅。只有父類被初始化。竟闪。對(duì)于靜態(tài)字段离福,只有直接定義這個(gè)字段的類才會(huì)被初始化.
- 通過數(shù)組定義來引用類,不會(huì)觸發(fā)類的初始化
SubClass[] sca = new SubClass[10];// 被動(dòng)引用2 - 訪問類的常量炼蛤,不會(huì)初始化類
具體可參考 https://www.cnblogs.com/javaee6/p/3714716.html
JVM 加載 class 文件的原理機(jī)制
JVM 中類的裝載是由類加載器(ClassLoader) 和它的子類來實(shí)現(xiàn)的妖爷,Java 中的類加載器是一個(gè)重要的 Java 運(yùn)行時(shí)系統(tǒng)組件,它負(fù)責(zé)在運(yùn)行時(shí)查找和裝入類文件中的類理朋。
由于 Java 的跨平臺(tái)性絮识,經(jīng)過編譯的 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)證帝簇、準(zhǔn)備(為靜態(tài)變量分配內(nèi)存并設(shè)置默認(rèn)的初始值)和解析(將符號(hào)引用替換為直接引用)三個(gè)步驟徘郭。最后 JVM 對(duì)類進(jìn)行初始化靠益,包括:
- 如果類存在直接的父類并且這個(gè)類還沒有被初始化,那么就先初始化父類崎岂;
- 如果類中存在初始化語(yǔ)句捆毫,就依次執(zhí)行這些初始化語(yǔ)句。
類的加載是由類加載器完成的冲甘,類加載器包括:?jiǎn)?dòng)類加載器(BootStrap)绩卤、擴(kuò)展加載器(Extension)、應(yīng)用程序加載器(Application)和用戶自定義類加載器(java.lang.ClassLoader的子類)江醇。從JDK 1.2開始濒憋,類加載過程采取了父親委托機(jī)制(PDM)。PDM 更好的保證了 Java 平臺(tái)的安全性陶夜,在該機(jī)制中凛驮,JVM 自帶的 Bootstrap 是根加載器,其他的加載器都有且僅有一個(gè)父類加載器条辟。類的加載首先請(qǐng)求父類加載器加載黔夭,父類加載器無能為力時(shí)才由其子類加載器自行加載。JVM 不會(huì)向 Java 程序提供對(duì) Bootstrap 的引用羽嫡。下面是關(guān)于幾個(gè)類加載器的說明:
- Bootstrap:?jiǎn)?dòng)類加載器本姥,一般用本地代碼實(shí)現(xiàn),負(fù)責(zé)加載JVM基礎(chǔ)核心類庫(kù)杭棵。加載存放在<JAVA_HOME>/lib目錄中的類庫(kù)(如rt.jar)婚惫;
- Extension ClassLoader:擴(kuò)展加載器, 負(fù)責(zé)加載<JAVA_HOME>/lib/ext目錄中的 魂爪,或被java.ext.dirs 系統(tǒng)屬性所指定的目錄中加載類庫(kù)先舷,它的父加載器是 Bootstrap;
- Application ClassLoader:應(yīng)用程序加載器滓侍,其父類是Extension蒋川。它是應(yīng)用最廣泛的類加載器。它從環(huán)境變量 classpath 或者系統(tǒng)屬性 java.class.path 所指定的目錄中記載類撩笆,是用戶自定義加載器的默認(rèn)父加載器尔破。
缺點(diǎn):
- 雙親委派模型很好地解決了各個(gè)類加載器的基礎(chǔ)類統(tǒng)一問題(越基礎(chǔ)的類由越上層的加載器進(jìn)行加載),基礎(chǔ)類之所以被稱為“基礎(chǔ)”浇衬,是因?yàn)樗鼈兛偸亲鳛楸徽{(diào)用代碼調(diào)用的API。但是餐济,如果基礎(chǔ)類又要調(diào)用用戶的代碼時(shí)耘擂,雙親委派模型無法滿足要求。 因?yàn)锽ootstrap加載器無法找到永不代碼類絮姆。
為了解決這個(gè)困境醉冤,Java設(shè)計(jì)團(tuán)隊(duì)只好引入了一個(gè)不太優(yōu)雅的設(shè)計(jì):線程上下文件類加載器(Thread Context ClassLoader)秩霍。這個(gè)類加載器可以通過java.lang.Thread類的setContextClassLoader()方法進(jìn)行設(shè)置,如果創(chuàng)建線程時(shí)還未設(shè)置蚁阳,它將會(huì)從父線程中繼承一個(gè)铃绒;如果在應(yīng)用程序的全局范圍內(nèi)都沒有設(shè)置過,那么這個(gè)類加載器默認(rèn)就是應(yīng)用程序類加載器螺捐。了有線程上下文類加載器颠悬,JNDI服務(wù)使用這個(gè)線程上下文類加載器去加載所需要的SPI代碼,也就是父類加載器請(qǐng)求子類加載器去完成類加載動(dòng)作定血,這種行為實(shí)際上就是打通了雙親委派模型的層次結(jié)構(gòu)來逆向使用類加載器赔癌,已經(jīng)違背了雙親委派模型,但這也是無可奈何的事情澜沟。Java中所有涉及SPI的加載動(dòng)作基本上都采用這種方式灾票,例如JNDI,JDBC,JCE,JAXB和JBI等。 Dubbo的SPI也是采用這種機(jī)制實(shí)現(xiàn)茫虽。
雙親委派模型
除了頂層的啟動(dòng)類加載器外,其余的類加載器都應(yīng)當(dāng)有自己的父類加載器.順序依次是:
- Bootstrap ClassLoader: 啟動(dòng)類加載器,加載java_home/lib中的類
- Extension ClassLoader: 擴(kuò)展類加載器,加載java_home/lib/ext目錄下的類庫(kù)
- Application ClassLoader: 應(yīng)用程序類加載器,加載用戶類路徑上指定類庫(kù).
雙親委派模型的工作原理是:如果一個(gè)類加載器受到了類加載請(qǐng)求,它首先不會(huì)自己去嘗試加載這個(gè)類,而把這個(gè)請(qǐng)求委派給父類加載器去完成,每一層次的類加載器都是如此,因此所有的加載請(qǐng)求最終都應(yīng)該傳送到頂層的啟動(dòng)類加載器中,只有當(dāng)父類加載器反饋?zhàn)约簾o法完成加載請(qǐng)求時(shí),加載器才嘗試自己加載.這種方式保證了Oject類(JDK 核心類)在各個(gè)加載器加載環(huán)境中都是同一個(gè)類.
分派:靜態(tài)分派與動(dòng)態(tài)分派刊苍。
多態(tài)性特征的一些最基本的體現(xiàn). 靜態(tài)類型是編譯期可知的,動(dòng)態(tài)類型是在運(yùn)行時(shí)可知.Human h =new Man(); Human是靜態(tài)類型,Man時(shí)動(dòng)態(tài)類型.
所有依賴于靜態(tài)類型定位方法執(zhí)行版本的分派動(dòng)作稱作靜態(tài)分派,最典型的應(yīng)用是方法重載.靜態(tài)分派發(fā)生在編譯階段。
動(dòng)態(tài)分派是根據(jù)動(dòng)態(tài)類型來確定執(zhí)行的版本,所以只有到運(yùn)行時(shí)才能確定具體的執(zhí)行方法版本.典型的代表時(shí)重寫.其過程如下:
- 首先找到操作數(shù)棧棧頂?shù)牡谝粋€(gè)元素所執(zhí)向?qū)ο蟮膶?shí)際類型,記做C.
- 如果在類型C中找到和常量中的描述符和簡(jiǎn)單名稱都相符的方法,則進(jìn)行范圍權(quán)限校驗(yàn).如果通過則返回該方法的直接引用,否則拋出IllegalAccessError異常.
- 否則按照繼承關(guān)系從下往上一次對(duì)C的各個(gè)父類進(jìn)行第2步的搜索和驗(yàn)證過程.
- 如果始終沒有找到就拋出AbstractMethodError異常. 方法的接受者和方法的參數(shù)統(tǒng)稱方法宗量,根據(jù)分配基于多少中宗量可以分為單分派和多分派.java是靜態(tài)多分派,動(dòng)態(tài)分派屬于單分派.
動(dòng)態(tài)分派的實(shí)現(xiàn): 動(dòng)態(tài)分派時(shí)非常頻繁的動(dòng)作,而且動(dòng)態(tài)分派的方法版本選擇過程需要運(yùn)行時(shí)在類的方法元數(shù)據(jù)中搜索合適的目標(biāo)方法,因此出于性能的考慮,在方法區(qū)中建立一個(gè)虛方法表,用來保存各個(gè)方法的實(shí)際入口地址.如果某個(gè)方法的子類中沒有被重寫,那么子類的虛方法表里面的地址入口和父類相同方法的地址入口是一致的.都是指向父類的實(shí)現(xiàn)入口,如果子類中重寫了這個(gè)方法,子類方法表中的地址將會(huì)被替換為指向子類實(shí)現(xiàn)版本的入口地址.虛方法表在類加載的連接階段進(jìn)行初始化.
Student s= new Student(),在內(nèi)存中做了那些事情
- 加載Student.class 文件進(jìn)內(nèi)存
- 在棧內(nèi)存為s開辟空間
- 在堆內(nèi)存為Student對(duì)象開辟空間
- 學(xué)生對(duì)象的成員變量進(jìn)行顯示初始化
- 通過構(gòu)造方法對(duì)學(xué)生對(duì)象變量賦值
- 學(xué)生對(duì)象初始完畢濒析,把對(duì)象地址賦值給s變量
相關(guān)資料: http://blog.csdn.net/wisgood/article/details/16818243
Java對(duì)象正什,類存在形式
Java雙親委派模型及破壞
https://blog.csdn.net/zhangcanyan/article/details/78993959
雙親委派模型的式作過程是:如果一個(gè)類加載器收到了類加載的請(qǐng)求,它首先不會(huì)自己去嘗試加載這個(gè)類悼枢,而是把這個(gè)請(qǐng)求委派給父類加載器去完成埠忘,每一個(gè)層次的類加載器都是如此,因此所有的加載請(qǐng)求最終都應(yīng)該傳送到頂層的啟動(dòng)類加載器中馒索,只有當(dāng)父加載器反饋?zhàn)约簾o法完全這個(gè)加載請(qǐng)求時(shí)莹妒,子加載器才會(huì)嘗試自己去加載。
雙親委派模型的破壞
雙親委派模型的第一次“被破壞”其實(shí)發(fā)生在雙親委派模型出現(xiàn)之前--即JDK1.2發(fā)布之前绰上。由于雙親委派模型是在JDK1.2之后才被引入的旨怠,而類加載器和抽象類java.lang.ClassLoader則是JDK1.0時(shí)候就已經(jīng)存在,面對(duì)已經(jīng)存在 的用戶自定義類加載器的實(shí)現(xiàn)代碼蜈块,Java設(shè)計(jì)者引入雙親委派模型時(shí)不得不做出一些妥協(xié)鉴腻。為了向前兼容,JDK1.2之后的java.lang.ClassLoader添加了一個(gè)新的proceted方法findClass()百揭,在此之前爽哎,用戶去繼承java.lang.ClassLoader的唯一目的就是重寫loadClass()方法,因?yàn)樘摂M在進(jìn)行類加載的時(shí)候會(huì)調(diào)用加載器的私有方法loadClassInternal()器一,而這個(gè)方法的唯一邏輯就是去調(diào)用自己的loadClass()课锌。JDK1.2之后已不再提倡用戶再去覆蓋loadClass()方法,應(yīng)當(dāng)把自己的類加載邏輯寫到findClass()方法中,在loadClass()方法的邏輯里渺贤,如果父類加載器加載失敗雏胃,則會(huì)調(diào)用自己的findClass()方法來完成加載,這樣就可以保證新寫出來的類加載器是符合雙親委派模型的志鞍。
雙親委派模型的第二次“被破壞”是這個(gè)模型自身的缺陷所導(dǎo)致的瞭亮,雙親委派模型很好地解決了各個(gè)類加載器的基礎(chǔ)類統(tǒng)一問題(越基礎(chǔ)的類由越上層的加載器進(jìn)行加載),基礎(chǔ)類之所以被稱為“基礎(chǔ)”固棚,是因?yàn)樗鼈兛偸亲鳛楸徽{(diào)用代碼調(diào)用的API统翩。但是,如果基礎(chǔ)類又要調(diào)用用戶的代碼玻孟,那該怎么辦呢唆缴。
這并非是不可能的事情,一個(gè)典型的例子便是JNDI服務(wù)黍翎,它的代碼由啟動(dòng)類加載器去加載(在JDK1.3時(shí)放進(jìn)rt.jar)面徽,但JNDI的目的就是對(duì)資源進(jìn)行集中管理和查找,它需要調(diào)用獨(dú)立廠商實(shí)現(xiàn)部部署在應(yīng)用程序的classpath下的JNDI接口提供者(SPI, Service Provider Interface)的代碼匣掸,但啟動(dòng)類加載器不可能“認(rèn)識(shí)”之些代碼趟紊,該怎么辦?
為了解決這個(gè)困境碰酝,Java設(shè)計(jì)團(tuán)隊(duì)只好引入了一個(gè)不太優(yōu)雅的設(shè)計(jì):線程上下文件類加載器(Thread Context ClassLoader)霎匈。這個(gè)類加載器可以通過java.lang.Thread類的setContextClassLoader()方法進(jìn)行設(shè)置,如果創(chuàng)建線程時(shí)還未設(shè)置送爸,它將會(huì)從父線程中繼承一個(gè)铛嘱;如果在應(yīng)用程序的全局范圍內(nèi)都沒有設(shè)置過,那么這個(gè)類加載器默認(rèn)就是應(yīng)用程序類加載器袭厂。了有線程上下文類加載器墨吓,JNDI服務(wù)使用這個(gè)線程上下文類加載器去加載所需要的SPI代碼,也就是父類加載器請(qǐng)求子類加載器去完成類加載動(dòng)作纹磺,這種行為實(shí)際上就是打通了雙親委派模型的層次結(jié)構(gòu)來逆向使用類加載器帖烘,已經(jīng)違背了雙親委派模型,但這也是無可奈何的事情橄杨。Java中所有涉及SPI的加載動(dòng)作基本上都采用這種方式秘症,例如JNDI,JDBC,JCE,JAXB和JBI等。
雙親委派模型的第三次“被破壞”是由于用戶對(duì)程序的動(dòng)態(tài)性的追求導(dǎo)致的式矫,例如OSGi的出現(xiàn)乡摹。在OSGi環(huán)境下,類加載器不再是雙親委派模型中的樹狀結(jié)構(gòu)采转,而是進(jìn)一步發(fā)展為網(wǎng)狀結(jié)構(gòu)趟卸。