01-JVM原理

通過編譯器將源代碼編譯稱為字節(jié)碼文件(包含程序運行各類信息的數(shù)據(jù)文件),字節(jié)碼文件被類加載器加載到內(nèi)存中,在內(nèi)存中將各類數(shù)據(jù)分類整理媒熊,最后在解釋器的作用下帽哑,將程序運行起來谜酒。

1.靜態(tài)變量應(yīng)在邏輯的方法區(qū)上(即類變量) 2.方法區(qū)中的“字節(jié)碼”修改為“指令碼”比較合適

一、字節(jié)碼規(guī)范

JVM不關(guān)心類文件的來源妻枕,可源于java 僻族、JRuby、Groovy程序等屡谐;并且類文件并非依賴于磁盤文件述么,可以只存在內(nèi)存中。

class文件跟xml一樣是用來存儲數(shù)據(jù)的愕掏。類文件由“無符號數(shù)”度秘、”表“組成。類文件規(guī)范如下:

ClassFile {
    u4             magic; //Class 文件的標志
    u2             minor_version;//Class 的小版本號
    u2             major_version;//Class 的大版本號
    u2             constant_pool_count;//常量池的數(shù)量
    cp_info        constant_pool[constant_pool_count-1];//常量池
    u2             access_flags;//Class 的訪問標記
    u2             this_class;//當前類
    u2             super_class;//父類
    u2             interfaces_count;//接口
    u2             interfaces[interfaces_count];//一個類可以實現(xiàn)多個接口
    u2             fields_count;//Class 文件的字段屬性
    field_info     fields[fields_count];//一個類可以有多個字段
    u2             methods_count;//Class 文件的方法數(shù)量
    method_info    methods[methods_count];//一個類可以有個多個方法
    u2             attributes_count;//此類的屬性表中的屬性數(shù)
    attribute_info attributes[attributes_count];//屬性表集合
}

存放的數(shù)據(jù)和位置:

  • 字面量和符號引用存放在常量池中:cp_info饵撑;
  • 類的訪問標志(private剑梳、public等)存放在:access_flags;
  • 類信息(全限定名)存放在:this_class滑潘、super_class
  • 字段信息(字段作用域垢乙、是否static、final众羡、volatile侨赡、transient、數(shù)據(jù)類型粱侣、字段名稱)存放在field_info羊壹;
  • 方法信息存放在:method_info。注意方法的具體代碼(字節(jié)碼指令)是存放在attribute_info的Code屬性中齐婴。

常量池:17種常量類型且每種類型有完整獨立的數(shù)據(jù)結(jié)構(gòu)油猫,每一項都是一個表,互相間沒有共性無聯(lián)系柠偶,每個數(shù)據(jù)結(jié)構(gòu)的第一位都是標識位情妖。

屬性表集合:虛擬機規(guī)范一共制定了29項;與常量池類似诱担,每個屬性都有自己的存儲結(jié)構(gòu)毡证。

根據(jù)虛擬機的字節(jié)碼文件引出以下問題:
1. 虛擬機如何將源代碼編譯為字節(jié)碼?
2. 虛擬機如何字節(jié)碼文件加載到內(nèi)存中蔫仙?
3. 字節(jié)碼加載到內(nèi)存后料睛,如何讓程序運行起來呢?

二、編譯階段

  1. 虛擬機如何將源代碼編譯為字節(jié)碼文件恤煞?

將源代碼編譯為字節(jié)碼是由編譯器完成的屎勘; 根據(jù)編譯期的不同分為:前端(javac)、即時(C1和C2)和提前(Jaotc)編譯器居扒。提前編譯器是直接把程序編譯成與目標機器指令集相關(guān)的二進制代碼概漱。

前端編譯器

需要注意的是前端編譯器如Javac,對代碼的運行效率幾乎沒有任何優(yōu)化措施,因為虛擬機設(shè)計團隊選擇把對性能的優(yōu)化集中到運行期喜喂,這樣可以讓那些不是由javac產(chǎn)生的字節(jié)碼文件(如JRuby瓤摧、Groovy程序)也能享受到編譯器優(yōu)化所帶來的性能提升。但是javac這類編譯器可以通過“語法糖”提高編碼效率夜惭。

“語法糖”:目的是提高代碼開發(fā)效率姻灶。

以下是Java支持的語法糖:

  • 泛型:Java采用的是“類型擦除式泛型”,即泛型只存在于源碼中诈茧,編譯后的字節(jié)碼中全部泛型被替換成為原來的裸類型产喉,并在相應(yīng)的地方插入了強制轉(zhuǎn)型代碼。在運行期間ArrayList<Integer>和ArrayList<String>是一個類型(方法重載時這兩個類型屬于同一個類型敢会,編譯報錯)曾沈。這樣做的缺點有兩個:1.運行期間無法得到泛型類型信息,會讓代碼變得繁瑣鸥昏。2.泛型擦除后塞俱,導(dǎo)致ArrayList<int>這種類型不支持,因為基礎(chǔ)數(shù)據(jù)類型不能與Object互轉(zhuǎn)吏垮。
  • lambda表達式:不算純粹的語法糖障涯,但是在前端編譯器中做了大量的轉(zhuǎn)換工作。
  • 其他語法糖:自動裝箱膳汪、拆箱唯蝶;循環(huán)遍歷;變長參數(shù)遗嗽;條件編譯粘我;內(nèi)部類;枚舉類痹换;斷言語句征字;數(shù)值字面量;對枚舉和字符串的switch支持娇豫;try語句中定義和關(guān)閉資源匙姜。

以Javac為例的前端編譯器:虛擬機規(guī)范對編譯的約束相當寬松,極端情況下會出現(xiàn)在javac中可以編譯但是在IDE中不能編譯冯痢。編譯主要分為:

  • 1 解析與填充符號表:
    1.1 詞法分析:是將源代碼的字符(程序編寫的最小單位)流轉(zhuǎn)變?yōu)闃擞洠ǔ绦蚓幾g的最小單位)集合的過程氮昧。
    1.2 語法分析:根據(jù)標記序列構(gòu)造抽象語法樹或详,抽象語法樹每個節(jié)點都代表一個語法結(jié)構(gòu),例如包郭计、類型、運算符椒振、接口昭伸、注釋都可以是一種特定的語法結(jié)構(gòu)。
    1.3 完成詞法和語法分析后澎迎,需要對符號表進行填充庐杨;符號表是由一組符號地址和符號信息構(gòu)成的數(shù)據(jù)結(jié)構(gòu),類似于哈希表中鍵值對的存儲形式夹供。

  • 2 注解處理器執(zhí)行:注解在設(shè)計上原本與代碼一樣只會在運行期間起作用灵份,但在JDK6后提出“插入式注解處理器”的API,可以將注解提前在編譯期對代碼中的特定注解進行處理哮洽√钋可以將“插入式注解處理器”看作是一組編譯器的插件,這些插件工作時允許讀取鸟辅、修改和添加抽象語法樹的任意元素氛什。如插件Lombok就是利用“插入式注解處理器”API干預(yù)編譯器的行為。

  • 3 語義分析和字節(jié)碼生成:
    3.1 語義分析:抽象樹能夠表示一個正確的源程序匪凉,但無法保證程序的語義符合邏輯枪眉。語義分析就是對結(jié)構(gòu)上正確源程序進行上下文相關(guān)性質(zhì)的檢查。
    3.2 語法糖解析:語法糖避免的代碼的“啰嗦”和減少語法“錯誤”再层,但是在編譯期間還是需要將“語法糖”恢復(fù)原來的語法贸铜。javac中通過desugar()完成解析。
    3.3 字節(jié)碼生成:字節(jié)碼生成不見將前面步驟所生成的信息轉(zhuǎn)化成字節(jié)碼寫道磁盤聂受,同時還進行少量的代碼添加和轉(zhuǎn)化工作蒿秦。例如實例的構(gòu)造器<init>()。

即時編譯器

按照虛擬機規(guī)范饺饭,即時編譯器和提前編譯器都是非必需的渤早。

即時編譯器:用到即時編譯器的時候,虛擬機已經(jīng)完成類的加載過程瘫俊,當字節(jié)碼文件進入內(nèi)存后鹊杖,Java程序最初先是通過解釋器執(zhí)行的,當虛擬機發(fā)現(xiàn)某個方法或代碼塊運行的特別頻繁扛芽,就認定這些代碼是“熱點代碼”(多次被調(diào)用和多次被執(zhí)行的循環(huán)體)骂蓖,為了提高熱點代碼的效率,虛擬機通過即時編譯器進行代碼優(yōu)化(會改變字節(jié)碼文件)川尖。這種編譯方式因為發(fā)生在方法執(zhí)行過程中登下,被形象的稱為“棧上編譯”。

即時編譯器在HotSpot中內(nèi)置了三個,其中“客戶端編譯器(C1)”和“服務(wù)端編譯器(C2)”存在很久被芳,JDK10后出現(xiàn)了Graal編譯器缰贝。

在分層編譯模式之前,虛擬機都是采用一個解釋器一個編譯器搭配的工作(混合)模式畔濒;但是也可以通過設(shè)置是否采用混合模式剩晴。分層模式之后解釋器、客戶端編譯器(C1)和服務(wù)端編譯器(C2)會同時工作侵状,熱點代碼被多次編譯赞弥,用客戶端編譯器獲取更高的編譯速度,用服務(wù)端編譯器獲取更好的編譯質(zhì)量趣兄。

代碼被標記為熱點代碼的方式有兩種:

  • 基于采樣的熱點探測:虛擬機周期性的檢查各個線程的調(diào)用棧頂绽左,如果發(fā)現(xiàn)某個方法經(jīng)常出現(xiàn)在棧頂,那么這個方法就是熱點代碼艇潭。
  • 基于計數(shù)器的熱點探測:虛擬機會為每個方法建立計數(shù)器拼窥,如果某個方法執(zhí)行次數(shù)超過閾值,那么這個方法就是熱點代碼蹋凝。HotSpot為每個方法準備“方法調(diào)用計數(shù)器”和“回邊計數(shù)器”闯团,當?shù)竭_閾值后就會觸發(fā)編譯器進行優(yōu)化。

編譯器如何進行熱點代碼的優(yōu)化仙粱?

在后臺執(zhí)行編譯的過程房交,前端編譯器利用簡單快速的三段式編譯器,進行局部(如常量傳播伐割、空值檢查消除)優(yōu)化候味。而后端編譯器會執(zhí)行大部分經(jīng)典的優(yōu)化動作:無用代碼消除、循環(huán)展開隔心、循環(huán)表達式外移白群、消除公共子表達式、常量傳播硬霍、基本塊重排序帜慢、范圍檢查消除、空值檢查消除等唯卖。

提前編譯器

按照虛擬機規(guī)范粱玲,即時編譯器和提前編譯器都是非必需的。
提前編譯在JDK1.0時候就可以使用外掛的提前編譯拜轨。但是提前編譯在很長時間內(nèi)沒有進展和應(yīng)用抽减;直到2013,在Android世界中出現(xiàn)了ART使用提前編譯橄碾,把使用即時編譯的Dalvik徹底終結(jié)卵沉。提前編譯器才大展身手颠锉。

三、加載階段

  1. 虛擬機如何字節(jié)碼文件加載到內(nèi)存中史汗?

通過類加載將字節(jié)碼的數(shù)據(jù)加載到內(nèi)存中琼掠。類的加載分為:加載、連接(驗證停撞、準備眉枕、解析)、初識化怜森、使用、和卸載谤牡。其中解析可不按照該順序副硅。

類的加載過程:

  • 加載:通過類的全限定名來獲取定義該類的二進制流;然后將字節(jié)碼中所代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)換為方法區(qū)運行時的數(shù)據(jù)結(jié)構(gòu)翅萤。注意:數(shù)據(jù)已經(jīng)從字節(jié)碼轉(zhuǎn)移到內(nèi)存中的方法區(qū)了恐疲。
  • 驗證:驗證文件格式、驗證元數(shù)據(jù)套么、字節(jié)碼驗證(Code屬性的數(shù)據(jù))培己、最后是符號引用驗證。
  • 準備:將類中定義的變量分配到方法區(qū)并初始化胚泌;這里分配僅僅包括類變量(static修飾的)省咨,而不包括實例變量,實例變量會在對象實例化的時候隨著對象一起分配到堆內(nèi)存中玷室。
  • 解析:將字節(jié)碼中常量池中的符號引用替換直接引用零蓉。
  • 初始化:在準備階段變量被賦〇值,在初始化階段中會根據(jù)程序員通過程序編碼制定主觀計劃去初始化類變量和其他資源穷缤。

類的加載器

對于任意一個類敌蜂,都必須由加載它的加載器+類本身確定在虛擬機中的唯一性。
對于虛擬機來說只有兩類加載器:啟動類加載器和其他類加載器津肛。在程序員角度Java一直保持著三層類加載器章喉、雙親委派的類加載架構(gòu)。

三層類加載器身坐、雙親委派

啟動類加載器:負責加載JAVA_HOME\lib下的類秸脱。
擴展類加載器:負責加載JAVA_HOME\lib\ext下的類。
應(yīng)用程序加載器:負責加載用戶類路徑下的所有類部蛇。
雙親委派:一個類加載器收到加載類的請求后撞反,首先將請求委派給父類加載器,每個層次的類加載器都是如此搪花;最終請求會被傳給啟動類加載器遏片,只有當父類加載器反饋無法完成這個加載請求時嘹害,子加載器才會嘗試自己去加載該類。這樣的好處是Object在程序的各類中都能保證是同一個類吮便。

四笔呀、運行階段

  1. 字節(jié)碼加載到內(nèi)存后,如何讓程序運行起來呢髓需?
  • 將字節(jié)碼中的數(shù)據(jù)分類放入內(nèi)存中的不同區(qū)域许师。
  • 虛擬機解釋器解釋棧中的指令碼,利用棧中的“操作數(shù)椓糯遥”執(zhí)行的指令(入棧進行計算微渠,出棧則存入局部變量表)。
棧為主的引擎
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末咧擂,一起剝皮案震驚了整個濱河市逞盆,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌松申,老刑警劉巖云芦,帶你破解...
    沈念sama閱讀 222,590評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異贸桶,居然都是意外死亡舅逸,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,157評論 3 399
  • 文/潘曉璐 我一進店門皇筛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來琉历,“玉大人,你說我怎么就攤上這事水醋∩埔眩” “怎么了?”我有些...
    開封第一講書人閱讀 169,301評論 0 362
  • 文/不壞的土叔 我叫張陵离例,是天一觀的道長换团。 經(jīng)常有香客問我,道長宫蛆,這世上最難降的妖魔是什么艘包? 我笑而不...
    開封第一講書人閱讀 60,078評論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮耀盗,結(jié)果婚禮上想虎,老公的妹妹穿的比我還像新娘。我一直安慰自己叛拷,他們只是感情好舌厨,可當我...
    茶點故事閱讀 69,082評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著忿薇,像睡著了一般裙椭。 火紅的嫁衣襯著肌膚如雪弄跌。 梳的紋絲不亂的頭發(fā)上加叁,一...
    開封第一講書人閱讀 52,682評論 1 312
  • 那天未巫,我揣著相機與錄音约急,去河邊找鬼。 笑死炊汤,一個胖子當著我的面吹牛正驻,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播抢腐,決...
    沈念sama閱讀 41,155評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼姑曙,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了迈倍?” 一聲冷哼從身側(cè)響起伤靠,我...
    開封第一講書人閱讀 40,098評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎授瘦,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體竟宋,經(jīng)...
    沈念sama閱讀 46,638評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡提完,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,701評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了丘侠。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片徒欣。...
    茶點故事閱讀 40,852評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖蜗字,靈堂內(nèi)的尸體忽然破棺而出打肝,到底是詐尸還是另有隱情,我是刑警寧澤挪捕,帶...
    沈念sama閱讀 36,520評論 5 351
  • 正文 年R本政府宣布粗梭,位于F島的核電站,受9級特大地震影響级零,放射性物質(zhì)發(fā)生泄漏断医。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,181評論 3 335
  • 文/蒙蒙 一奏纪、第九天 我趴在偏房一處隱蔽的房頂上張望鉴嗤。 院中可真熱鬧,春花似錦序调、人聲如沸醉锅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,674評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽硬耍。三九已至垄琐,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間默垄,已是汗流浹背此虑。 一陣腳步聲響...
    開封第一講書人閱讀 33,788評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留口锭,地道東北人朦前。 一個月前我還...
    沈念sama閱讀 49,279評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像鹃操,于是被迫代替她去往敵國和親韭寸。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,851評論 2 361

推薦閱讀更多精彩內(nèi)容