????????最近在騰訊學(xué)堂聽了一下JVM入門到入魔的公開課后,總結(jié)了一下JVM修煉魔功心法,給看帖的簡書友參考參考,雖然功法簡單通俗,但是望大家別噴哈:
一、JVM是什么
? ??????JVM是java程序的核心和基礎(chǔ),是介于java編譯器和oa操作平臺之間的虛擬機(jī)處理器。java編譯器會(huì)把java文件編譯成jvm能識別的字節(jié)碼文件,然后通過jvm解析和生成一條條os平臺可識別的操作指令,然后發(fā)送給特定平臺并運(yùn)行。
二、JRE、JVM、JDK三者的關(guān)系是什么
? ? ? ? JDK是Java程序員常用的開發(fā)包、目的就是用來編譯和調(diào)試Java程序的。JRE是指Java運(yùn)行環(huán)境,也就是我們的寫好的程序必須在JRE才能夠運(yùn)行。JVM是Java虛擬機(jī)的縮寫,是指負(fù)責(zé)將字節(jié)碼解釋成為特定的機(jī)器碼進(jìn)行運(yùn)行,值得注意的是在運(yùn)行過程中,Java源程序需要通過編譯器編譯為.class文件,否則JVM不認(rèn)識。具體關(guān)系如下圖,此圖是從別人網(wǎng)站扣來的:

三、JVM的體系結(jié)構(gòu)
1.???????類裝載器(ClassLoader)(用來裝載.class文件)
2.???????執(zhí)行引擎(執(zhí)行字節(jié)碼,或者執(zhí)行本地方法)
3.???????運(yùn)行時(shí)數(shù)據(jù)區(qū)(方法區(qū)、堆、java棧、PC寄存器、本地方法棧)
四、類裝載器

1、類的加載:第一步根據(jù)類的權(quán)限名找到此類的二進(jìn)制字節(jié)流,第二步把該字節(jié)流所代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)變?yōu)榉椒▍^(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu),第三步在java堆中生成一個(gè)java.lang.class對象,作為方法區(qū)這些數(shù)據(jù)的訪問入口。
2、鏈接主要分為三部分:
第一部分是驗(yàn)證,也就是校驗(yàn)字節(jié)流文件包含的信息是否符合當(dāng)前虛擬機(jī)的要求,并且不會(huì)危害當(dāng)前虛擬機(jī)。驗(yàn)證階段主要包括四個(gè)檢驗(yàn)過程:文件格式驗(yàn)證、元數(shù)據(jù)驗(yàn)證、字節(jié)碼驗(yàn)證和符號引用驗(yàn)證。
第二部分是準(zhǔn)備,通俗點(diǎn)說就是給類里面的靜態(tài)變量初始化一個(gè)值,例如public static int a =12;這句類里面定義初始化的類變量會(huì)在準(zhǔn)備階段初始化為0.
第三部分是解析,簡單來說就是把虛擬機(jī)常量池中的符號引用轉(zhuǎn)變?yōu)橹苯右谩?/p>
符號引用就是一些代表著類的方法、變量等的字面量,在反編譯的機(jī)器碼里可看到一些字面量,帶CONSTANT_開頭的字段就是一種符號變量。通俗點(diǎn)說就是:您是某省某城市里的帥鍋或美女。
直接引用就是直接指向目標(biāo)對象的指針、相對偏移量或是一個(gè)能間接定位到目標(biāo)的句柄。通俗點(diǎn)解釋就是:您是某省某城市里的叫XXX的帥鍋或美女,也就是具體指向到具體對象了。
3、初始化:就是給該類進(jìn)行初始化成一個(gè)實(shí)例。有四種情況會(huì)觸發(fā)初始化:①直接執(zhí)行new語句;②給該類的子類進(jìn)行初始化;③使用反射的newInstance進(jìn)行創(chuàng)建對象;④java程序啟動(dòng)時(shí)指定加載初始化的類。
4、額外科普一下類加載器有哪幾種吧?

①、啟動(dòng)類加載器(Bootstrap ClassLoader):這個(gè)類加載器負(fù)責(zé)放在<JAVA_HOME>\lib目錄中的rt.jar,或者被-Xbootclasspath參數(shù)所指定的路徑中的,并且是虛擬機(jī)識別的類庫。用戶無法直接使用。
②、擴(kuò)展類加載器(Extension ClassLoader):這個(gè)類加載器由sun.misc.Launcher$AppClassLoader實(shí)現(xiàn)。它負(fù)責(zé)<JAVA_HOME>\lib\ext目錄中的*.jar,或者被java.ext.dirs系統(tǒng)變量所指定的路徑中的所有類庫。用戶可以直接使用。
③、應(yīng)用程序類加載器(Application ClassLoader):這個(gè)類由sun.misc.Launcher$AppClassLoader實(shí)現(xiàn)。是ClassLoader中g(shù)etSystemClassLoader()方法的返回值。它負(fù)責(zé)用戶路徑(ClassPath)所指定的類庫。用戶可以直接使用。如果用戶沒有自己定義類加載器,默認(rèn)使用這個(gè)。
④、自定義加載器(Custom ClassLoader):用戶自己定義的類加載器。
類的加載時(shí)使用的是雙親委派機(jī)制,也就是類加載會(huì)先讓其父類的加載器去加載,父類加載器沒加載成功,它所在的類加載器才會(huì)去加載。
五、JVM的執(zhí)行引擎
在執(zhí)行方法時(shí)JVM提供了四種指令來執(zhí)行:
(1)invokestatic:調(diào)用類的static方法
(2)invokevirtual:調(diào)用對象實(shí)例的方法
(3)invokeinterface:將屬性定義為接口來進(jìn)行調(diào)用
(4)invokespecial:JVM對于初始化對象(Java構(gòu)造器的方法為:)以及調(diào)用對象實(shí)例中的私有方法時(shí)。
字節(jié)碼可以通過以下兩種方式轉(zhuǎn)換成合適的語言:
(1)解釋器:一條一條地讀取,解釋并且執(zhí)行字節(jié)碼指令。因?yàn)樗粭l一條地解釋和執(zhí)行指令,所以它可以很快地解釋字節(jié)碼,但是執(zhí)行起來會(huì)比較慢。這是解釋執(zhí)行的語言的一個(gè)缺點(diǎn)。字節(jié)碼這種“語言”基本來說是解釋執(zhí)行的。第一代JVM使用。
(2)即時(shí)(Just-In-Time)編譯器:即時(shí)編譯器被引入用來彌補(bǔ)解釋器的缺點(diǎn)。執(zhí)行引擎首先按照解釋執(zhí)行的方式來執(zhí)行,然后在合適的時(shí)候,即時(shí)編譯器把整段字節(jié)碼編譯成本地代碼。然后,執(zhí)行引擎就沒有必要再去解釋執(zhí)行方法了,它可以直接通過本地代碼去執(zhí)行它。執(zhí)行本地代碼比一條一條進(jìn)行解釋執(zhí)行的速度快很多。編譯后的代碼可以執(zhí)行的很快,因?yàn)楸镜卮a是保存在緩存里的。第二代JVM使用
目前的JVM是兩種方式結(jié)合。
六、JVM運(yùn)行時(shí)數(shù)據(jù)區(qū)

1、方法區(qū)
? ? ? ? 方法區(qū)有時(shí)候也叫永久區(qū),存儲的是類的信息(包括類的修飾符、類的名稱、類里面定義的方法信息、字段信息),類中的靜態(tài)變量,類中定義的final常量,類的field信息以及編譯器編譯后的代碼等。通常開發(fā)者在程序中寫的getName方法獲取的數(shù)據(jù)就是來自于方法區(qū)的,所以方法區(qū)域是共享的,也是GC的重要區(qū)域之一。當(dāng)方法區(qū)需要的內(nèi)存大于其所允許的內(nèi)存,會(huì)拋出OutOfMemory的錯(cuò)誤信息。在方法區(qū)中還有一個(gè)比較重要的區(qū)域就是運(yùn)行時(shí)常量區(qū),用于存放靜態(tài)編譯產(chǎn)生的字面量和符號引用。運(yùn)行時(shí)生成的常量也會(huì)存在這個(gè)常量池中,比如String的intern方法。它是每一個(gè)類或接口的常量池的運(yùn)行時(shí)表示形式,在類和接口被加載到JVM后,對應(yīng)的運(yùn)行時(shí)常量池就被創(chuàng)建出來。
2、堆
? ? ? ? Java堆是用來存儲實(shí)例對象和數(shù)組(當(dāng)然數(shù)組的引用是存在棧中的)。堆是被所有線程共享的,也是GC重要回收區(qū)域,因此在此上面分配內(nèi)存需要加鎖,這樣沒new一個(gè)對象都會(huì)對jvm的開銷很大。Sun?Hotspot?JVM為了提升對象分配內(nèi)存的效率,特意單獨(dú)給每個(gè)新建的線程都提供一個(gè)TLAB空間,大小由JVM在運(yùn)行時(shí)進(jìn)行計(jì)算分配,該空間分配內(nèi)存不需要加鎖,因此每個(gè)線程new對象時(shí)都盡量在該空間給其分配內(nèi)存。當(dāng)然如果對象所需內(nèi)存太大還是需要堆內(nèi)存親自分配。堆內(nèi)存根據(jù)對象存活時(shí)間分為了young區(qū)和old區(qū),又因?yàn)镚C性能優(yōu)化,young區(qū)根據(jù)8:1:1分為了Eden區(qū)、survivor1和survivor2區(qū)。具體原因看下一篇GC機(jī)制的魔功心法。
3、虛擬機(jī)棧


? ? ? ? Java棧又稱之為虛擬機(jī)棧,是用來執(zhí)行java方法的,是線程私有的。每個(gè)線程執(zhí)行一個(gè)方法都會(huì)創(chuàng)建一個(gè)棧幀進(jìn)行壓棧,每個(gè)棧幀包括了局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈接、返回地址信息以及附加信息,當(dāng)方法執(zhí)行完畢,棧幀就會(huì)被出棧。
①、局部變量表:存的都是一些基本數(shù)據(jù)類型的數(shù)據(jù),如int,float,double,boolean,short,long,char,byte等,還有一些方法中聲明的非靜態(tài)變量或形參,對象的引用地址等。
②、操作數(shù)棧:棧最典型的一個(gè)應(yīng)用就是用來對表達(dá)式求值嘛,而程序的方法執(zhí)行,歸根到底就是一步步的計(jì)算過程嘛,因此不難理解,操作數(shù)棧就是用于該方法的執(zhí)行過程中的計(jì)算。
③、動(dòng)態(tài)鏈接:因?yàn)榉椒▓?zhí)行可能需要用到一些類定義的常量,此時(shí)需要一個(gè)指向它的引用,所以該步驟進(jìn)行的是把符號引用轉(zhuǎn)變成直接引用,體現(xiàn)于多態(tài)的應(yīng)用時(shí),子類在運(yùn)行時(shí)可能會(huì)執(zhí)行父類的方法,會(huì)執(zhí)行多一次把符號引用轉(zhuǎn)變成直接引用。
④、方法返回地址:就是存儲方法執(zhí)行完需要return返回的那個(gè)地址信息。
⑤、附加信息:一般不關(guān)注,因?yàn)樵撎幋娴氖穷愃茥I疃鹊男畔ⅲǜ鱾€(gè)虛擬機(jī)實(shí)現(xiàn)方式不同導(dǎo)致)。
4、本地方法棧
? ? ? ? JVM采用本地方法堆棧來支持native方法的執(zhí)行,此區(qū)域用于存儲每個(gè)native方法調(diào)用的狀態(tài)。native方法會(huì)是一些C++編譯打包成的底層方法之類。
5、程序計(jì)數(shù)器
? ? ? ? 程序計(jì)數(shù)器也被稱為PC寄存器,是線程私有的。
? ? ? ? 每個(gè)線程都會(huì)有一個(gè)程序計(jì)數(shù)器,CPU在任一時(shí)刻,會(huì)執(zhí)行某一個(gè)線程中的指令,因此需要一個(gè)計(jì)數(shù)器來存儲每個(gè)線程執(zhí)行到的哪一個(gè)指令以及下一個(gè)指令是什么,這樣才不會(huì)出現(xiàn)執(zhí)行亂套。JVM規(guī)范中,執(zhí)行非native方法時(shí),程序計(jì)數(shù)器存的是線程當(dāng)前執(zhí)行指令地址,執(zhí)行native方法時(shí),程序計(jì)數(shù)器存儲的地址是undefined。
? ? ? ? 總結(jié)一下,JVM表面的原理已經(jīng)這么多了,深層源碼會(huì)更復(fù)雜,人類的進(jìn)步真是越來越可怕啊。。。。。