做java開發(fā)的幾乎都知道jvm這個(gè)名詞管挟,但是由于jvm對(duì)實(shí)際的簡單開發(fā)的來說關(guān)聯(lián)的還是不多,一般工作個(gè)一兩年(當(dāng)然不包括愛學(xué)習(xí)的及專門做性能優(yōu)化的什么的)弄捕,很少有人能很好的去學(xué)習(xí)及理解什么是jvm僻孝,個(gè)人認(rèn)為這塊還是非常有必要去認(rèn)真了解及學(xué)習(xí)的导帝,這是java的基石。
JVM是什么穿铆?
JVM是Java Virtual Machine(Java虛擬機(jī))的縮寫您单,JVM是一種用于計(jì)算設(shè)備的規(guī)范,它是一個(gè)虛構(gòu)出來的計(jì)算機(jī)荞雏,是通過在實(shí)際的計(jì)算機(jī)上仿真模擬各種計(jì)算機(jī)功能來實(shí)現(xiàn)的虐秦。
對(duì)于JVM的學(xué)習(xí),在我看來這么幾個(gè)部分最重要:
Java代碼編譯和執(zhí)行的整個(gè)過程
JVM內(nèi)存管理及垃圾回收機(jī)制
Java代碼編譯和執(zhí)行的整個(gè)過程
Java代碼編譯是由Java源碼編譯器來完成凤优,流程圖如下所示:
Java字節(jié)碼的執(zhí)行是由JVM執(zhí)行引擎來完成悦陋,流程圖如下所示:
Java代碼編譯和執(zhí)行的整個(gè)過程包含了以下三個(gè)重要的機(jī)制:
Java源碼編譯機(jī)制
類加載機(jī)制
類執(zhí)行機(jī)制
如果大家有想學(xué)習(xí)了解的,可以加入我的Java高級(jí)架構(gòu)進(jìn)階學(xué)習(xí)群:671017482
Java源碼編譯機(jī)制
Java 源碼編譯由以下三個(gè)過程組成:(javac –verbose 輸出有關(guān)編譯器正在執(zhí)行的操作的消息)
分析和輸入到符號(hào)表
注解處理
語義分析和生成class文件
最后生成的class文件由以下部分組成:
結(jié)構(gòu)信息筑辨。包括class文件格式版本號(hào)及各部分的數(shù)量與大小的信息
元數(shù)據(jù)俺驶。對(duì)應(yīng)于Java源碼中聲明與常量的信息。包含類/繼承的超類/實(shí)現(xiàn)的接口的聲明信息棍辕、域與方法聲明信息和常量池
方法信息暮现。對(duì)應(yīng)Java源碼中語句和表達(dá)式對(duì)應(yīng)的信息。包含字節(jié)碼楚昭、異常處理器表栖袋、求值棧與局部變量區(qū)大小、求值棧的類型記錄抚太、調(diào)試符號(hào)信息
類加載機(jī)制
JVM的類加載是通過ClassLoader及其子類來完成的塘幅,類的層次關(guān)系和加載順序可以由下圖來描述:
1)Bootstrap ClassLoader /啟動(dòng)類加載器
$JAVA_HOME中jre/lib/rt.jar里所有的class,由C++實(shí)現(xiàn)尿贫,不是ClassLoader子類
2)Extension ClassLoader/擴(kuò)展類加載器
負(fù)責(zé)加載java平臺(tái)中擴(kuò)展功能的一些jar包电媳,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目錄下的jar包
3)App ClassLoader/ 系統(tǒng)類加載器
負(fù)責(zé)記載classpath中指定的jar包及目錄中class
4)Custom ClassLoader/用戶自定義類加載器(java.lang.ClassLoader的子類)
屬于應(yīng)用程序根據(jù)自身需要自定義的ClassLoader畔塔,如tomcat仿野、jboss都會(huì)根據(jù)j2ee規(guī)范自行實(shí)現(xiàn)ClassLoader
加載過程中會(huì)先檢查類是否被已加載稍算,檢查順序是自底向上,從Custom ClassLoader到BootStrap ClassLoader逐層檢查身冀,只要某個(gè)classloader已加載就視為已加載此類,保證此類只所有ClassLoader加載一次括享。而加載的順序是自頂向下搂根,也就是由上層來逐層嘗試加載此類。
類執(zhí)行機(jī)制
JVM是基于棧的體系結(jié)構(gòu)來執(zhí)行class字節(jié)碼的铃辖。線程創(chuàng)建后剩愧,都會(huì)產(chǎn)生程序計(jì)數(shù)器(PC)和棧(Stack),程序計(jì)數(shù)器存放下一條要執(zhí)行的指令在方法內(nèi)的偏移量娇斩,棧中存放一個(gè)個(gè)棧幀仁卷,每個(gè)棧幀對(duì)應(yīng)著每個(gè)方法的每次調(diào)用穴翩,而棧幀又是有局部變量區(qū)和操作數(shù)棧兩部分組成,局部變量區(qū)用于存放方法中的局部變量和參數(shù)锦积,操作數(shù)棧中用于存放方法執(zhí)行過程中產(chǎn)生的中間結(jié)果芒帕。
內(nèi)存管理和垃圾回收
JVM內(nèi)存組成結(jié)構(gòu)
JVM棧由堆、棧丰介、本地方法棧背蟆、方法區(qū)等部分組成,結(jié)構(gòu)圖如下所示:
JVM內(nèi)存回收
Sun的JVMGenerationalCollecting(垃圾回收)原理是這樣的:把對(duì)象分為年青代(Young)哮幢、年老代(Tenured)带膀、持久代(Perm),對(duì)不同生命周期的對(duì)象使用不同的算法橙垢。(基于對(duì)對(duì)象生命周期分析)
1.Young(年輕代)
年輕代分三個(gè)區(qū)垛叨。一個(gè)Eden區(qū),兩個(gè)Survivor區(qū)钢悲。大部分對(duì)象在Eden區(qū)中生成点额。當(dāng)Eden區(qū)滿時(shí),還存活的對(duì)象將被復(fù)制到Survivor區(qū)(兩個(gè)中的一個(gè))莺琳,當(dāng)這個(gè)Survivor區(qū)滿時(shí)还棱,此區(qū)的存活對(duì)象將被復(fù)制到另外一個(gè)Survivor區(qū),當(dāng)這個(gè)Survivor去也滿了的時(shí)候惭等,從第一個(gè)Survivor區(qū)復(fù)制過來的并且此時(shí)還存活的對(duì)象珍手,將被復(fù)制年老區(qū)(Tenured。需要注意辞做,Survivor的兩個(gè)區(qū)是對(duì)稱的琳要,沒先后關(guān)系,所以同一個(gè)區(qū)中可能同時(shí)存在從Eden復(fù)制過來對(duì)象秤茅,和從前一個(gè)Survivor復(fù)制過來的對(duì)象稚补,而復(fù)制到年老區(qū)的只有從第一個(gè)Survivor去過來的對(duì)象。而且框喳,Survivor區(qū)總有一個(gè)是空的课幕。
2.Tenured(年老代)
年老代存放從年輕代存活的對(duì)象。一般來說年老代存放的都是生命期較長的對(duì)象五垮。
3.Perm(持久代)
用于存放靜態(tài)文件乍惊,如今Java類、方法等放仗。持久代對(duì)垃圾回收沒有顯著影響润绎,但是有些應(yīng)用可能動(dòng)態(tài)生成或者調(diào)用一些class,例如Hibernate等,在這種時(shí)候需要設(shè)置一個(gè)比較大的持久代空間來存放這些運(yùn)行過程中新增的類莉撇。持久代大小通過-XX:MaxPermSize=進(jìn)行設(shè)置呢蛤。
舉個(gè)例子:當(dāng)在程序中生成對(duì)象時(shí),正常對(duì)象會(huì)在年輕代中分配空間稼钩,如果是過大的對(duì)象也可能會(huì)直接在年老代生成(據(jù)觀測(cè)在運(yùn)行某程序時(shí)候每次會(huì)生成一個(gè)十兆的空間用收發(fā)消息顾稀,這部分內(nèi)存就會(huì)直接在年老代分配)。年輕代在空間被分配完的時(shí)候就會(huì)發(fā)起內(nèi)存回收坝撑,大部分內(nèi)存會(huì)被回收静秆,一部分幸存的內(nèi)存會(huì)被拷貝至Survivor的from區(qū),經(jīng)過多次回收以后如果from區(qū)內(nèi)存也分配完畢巡李,就會(huì)也發(fā)生內(nèi)存回收然后將剩余的對(duì)象拷貝至to區(qū)抚笔。等到to區(qū)也滿的時(shí)候,就會(huì)再次發(fā)生內(nèi)存回收然后把幸存的對(duì)象拷貝至年老區(qū)侨拦。
通常我們說的JVM內(nèi)存回收總是在指堆內(nèi)存回收殊橙,確實(shí)只有堆中的內(nèi)容是動(dòng)態(tài)申請(qǐng)分配的,所以以上對(duì)象的年輕代和年老代都是指的JVM的Heap空間狱从,而持久代則是之前提到的MethodArea膨蛮,不屬于Heap。
現(xiàn)在我們就通過一個(gè)具體的例子來分析它的運(yùn)行過程季研。
虛擬機(jī)通過調(diào)用某個(gè)指定類的方法main啟動(dòng)敞葛,傳遞給main一個(gè)字符串?dāng)?shù)組參數(shù),使指定的類被裝載与涡,同時(shí)鏈接該類所使用的其它的類型惹谐,并且初始化它們。新建一java源文件并取名HelloApp.java驼卖,內(nèi)容如下:
class HelloApp {
public static void main(String[] args) {
System.out.println("Hello World!");
for (int i = 0; i < args.length; i++ ) {
System.out.println(args);
}
}
}
在命令模式下輸入:javac HelloApp.java 進(jìn)行編譯氨肌,這時(shí)同目錄下會(huì)產(chǎn)生一個(gè)編譯后的文件:HelloApp.class
然后在命令行模式下鍵入:java HelloApp run virtual machine
將通過調(diào)用HelloApp的方法main來啟動(dòng)java虛擬機(jī),傳遞給main一個(gè)包含三個(gè)字符串"run"酌畜、"virtual"怎囚、"machine"的數(shù)組。我們略述虛擬機(jī)在執(zhí)行HelloApp時(shí)可能采取的步驟桥胞。
JVM虛擬機(jī)運(yùn)行過程
開始試圖執(zhí)行類HelloApp的main方法恳守,發(fā)現(xiàn)該類并沒有被裝載,也就是說虛擬機(jī)當(dāng)前不包含該類的二進(jìn)制代表埠戳,于是虛擬機(jī)使用ClassLoader試圖尋找這樣的二進(jìn)制代表井誉。如果這個(gè)進(jìn)程失敗蕉扮,則拋出一個(gè)異常整胃。類被裝載后同時(shí)在main方法被調(diào)用之前,必須對(duì)類HelloApp與其它類型進(jìn)行鏈接然后初始化喳钟。
鏈接包含三個(gè)階段:檢驗(yàn)屁使,準(zhǔn)備和解析在岂。
檢驗(yàn)檢查被裝載的主類的符號(hào)和語義,準(zhǔn)備則創(chuàng)建類或接口的靜態(tài)域以及把這些域初始化為標(biāo)準(zhǔn)的默認(rèn)值蛮寂,解析負(fù)責(zé)檢查主類對(duì)其它類或接口的符號(hào)引用蔽午,在這一步它是可選的。類的初始化是對(duì)類中聲明的靜態(tài)初始化函數(shù)和靜態(tài)域的初始化構(gòu)造方法的執(zhí)行酬蹋。一個(gè)類在初始化之前它的父類必須被初始化及老。
哇哇咔,過完一個(gè)周末到現(xiàn)在還沒有緩過勁來范抓,你們有想特別了解的那些技術(shù)點(diǎn)嗎骄恶?
——源碼分析、Spring 企業(yè)級(jí)開發(fā)前瞻匕垫,持久層僧鲁,高性能/高并發(fā),分布式協(xié)調(diào)技術(shù) zookeeper 服務(wù)鎖象泵,Nosql寞秃,高可用性/可擴(kuò)展,分布式架構(gòu)介紹偶惠,服務(wù)調(diào)用春寿,性能優(yōu)化,JVM優(yōu)化洲鸠,數(shù)據(jù)庫優(yōu)化堂淡,服務(wù)器優(yōu)化,雙十一電商項(xiàng)目實(shí)戰(zhàn)(用戶認(rèn)證系統(tǒng)扒腕,商品管理系統(tǒng)绢淀,訂單系統(tǒng),支付系統(tǒng)等等)
留言給我瘾腰,我會(huì)優(yōu)先更新的皆的,也可以加我的群:671017482,群公告有大量的視頻教程蹋盆,謝謝大家费薄!