慢慢琢磨JVM
1 JVM簡(jiǎn)介
JVM是我們Javaer的最基本功底了,剛開始學(xué)Java的時(shí)候,一般都是從“Hello World”開始的,然后會(huì)寫個(gè)復(fù)雜點(diǎn)class,然后再找一些開源框架,比如Spring,Hibernate等等,再然后就開發(fā)企業(yè)級(jí)的應(yīng)用,比如網(wǎng)站、企業(yè)內(nèi)部應(yīng)用、實(shí)時(shí)交易系統(tǒng)等等,直到某一天突然發(fā)現(xiàn)做的系統(tǒng)咋就這么慢呢,而且時(shí)不時(shí)還來個(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)歷,那這些問題的最終根結(jié)是在哪呢?——?JVM。
JVM全稱是Java VirtualMachine,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)行的是否健壯、是否高效,接下來的部分就是重點(diǎn)介紹之。
2 JVM的組成部分
我們先把JVM這個(gè)虛擬機(jī)畫出來,如下圖所示:
從這個(gè)圖中可以看到,JVM是運(yùn)行在操作系統(tǒng)之上的,它與硬件沒有直接的交互。我們?cè)賮砜聪翵VM有哪些組成部分,如下圖所示:
?該圖參考了網(wǎng)上廣為流傳的JVM構(gòu)成圖,大家看這個(gè)圖,整個(gè)JVM分為四部分:
q??Class Loader?類加載器
類加載器的作用是加載類文件到內(nèi)存,比如編寫一個(gè)HelloWord.java程序,然后通過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_infoconstant_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_infoattributes[attributes_count];
????}
需要詳細(xì)了解的話,可以仔細(xì)閱讀《JVM Specification》的第四章“The class File Format”,這里不再詳細(xì)說明。
友情提示:Class Loader只管加載,只要符合文件結(jié)構(gòu)就加載,至于說能不能運(yùn)行,則不是它負(fù)責(zé)的,那是由Execution Engine負(fù)責(zé)的。
q??Execution Engine?執(zhí)行引擎
執(zhí)行引擎也叫做解釋器(Interpreter),負(fù)責(zé)解釋命令,提交操作系統(tǒng)執(zhí)行。
q??Native Interface本地接口
本地接口的作用是融合不同的編程語(yǔ)言為Java所用,它的初衷是融合C/C++程序,Java誕生的時(shí)候是C/C++橫行的時(shí)候,要想立足,必須有一個(gè)聰明的、睿智的調(diào)用C/C++程序,于是就在內(nèi)存中專門開辟了一塊區(qū)域處理標(biāo)記為native的代碼,它的具體做法是Native Method Stack中登記native方法,在Execution Engine執(zhí)行時(shí)加載native libraies。目前該方法使用的是越來越少了,除非是與硬件有關(guān)的應(yīng)用,比如通過Java程序驅(qū)動(dòng)打印機(jī),或者Java系統(tǒng)管理生產(chǎn)設(shè)備,在企業(yè)級(jí)應(yīng)用中已經(jīng)比較少見,因?yàn)楝F(xiàn)在的異構(gòu)領(lǐng)域間的通信很發(fā)達(dá),比如可以使用Socket通信,也可以使用Web Service等等,不多做介紹。
q??Runtime data area運(yùn)行數(shù)據(jù)區(qū)
運(yùn)行數(shù)據(jù)區(qū)是整個(gè)JVM的重點(diǎn)。我們所有寫的程序都被加載到這里,之后才開始運(yùn)行,Java生態(tài)系統(tǒng)如此的繁榮,得益于該區(qū)域的優(yōu)良自治,下一章節(jié)詳細(xì)介紹之。
?
整個(gè)JVM框架由加載器加載文件,然后執(zhí)行器在內(nèi)存中處理數(shù)據(jù),需要與異構(gòu)系統(tǒng)交互是可以通過本地接口進(jìn)行,瞧,一個(gè)完整的系統(tǒng)誕生了!
2 JVM的內(nèi)存管理
所有的數(shù)據(jù)和程序都是在運(yùn)行數(shù)據(jù)區(qū)存放,它包括以下幾部分:
q??Stack?棧
棧也叫棧內(nèi)存,是Java程序的運(yùn)行區(qū),是在線程創(chuàng)建時(shí)創(chuàng)建,它的生命期是跟隨線程的生命期,線程結(jié)束棧內(nèi)存也就釋放,對(duì)于棧來說不存在垃圾回收問題,只要線程一結(jié)束,該棧就Over。問題出來了:棧中存的是那些數(shù)據(jù)呢?又什么是格式呢?
棧中的數(shù)據(jù)都是以棧幀(Stack Frame)的格式存在,棧幀是一個(gè)內(nèi)存區(qū)塊,是一個(gè)數(shù)據(jù)集,是一個(gè)有關(guān)方法(Method)和運(yùn)行期數(shù)據(jù)的數(shù)據(jù)集,當(dāng)一個(gè)方法A被調(diào)用時(shí)就產(chǎn)生了一個(gè)棧幀F(xiàn)1,并被壓入到棧中,A方法又調(diào)用了B方法,于是產(chǎn)生棧幀F(xiàn)2也被壓入棧,執(zhí)行完畢后,先彈出F2棧幀,再?gòu)棾鯢1棧幀,遵循“先進(jìn)后出”原則。
那棧幀中到底存在著什么數(shù)據(jù)呢?棧幀中主要保存3類數(shù)據(jù):本地變量(LocalVariables),包括輸入?yún)?shù)和輸出參數(shù)以及方法內(nèi)的變量;棧操作(Operand Stack),記錄出棧、入棧的操作;棧幀數(shù)據(jù)(FrameData),包括類文件、方法等等。光說比較枯燥,我們畫個(gè)圖來理解一下Java棧,如下圖所示:
?圖示在一個(gè)棧中有兩個(gè)棧幀,棧幀2是最先被調(diào)用的方法,先入棧,然后方法2又調(diào)用了方法1,棧幀1處于棧頂?shù)奈恢?#xff0c;棧幀2處于棧底,執(zhí)行完畢后,依次彈出棧幀1和棧幀2,線程結(jié)束,棧釋放。
q??Heap?堆內(nèi)存
一個(gè)JVM實(shí)例只存在一個(gè)堆類存,堆內(nèi)存的大小是可以調(diào)節(jié)的。類加載器讀取了類文件后,需要把類、方法、常變量放到堆內(nèi)存中,以方便執(zhí)行器執(zhí)行,堆內(nèi)存分為三部分:
Permanent Space?永久存儲(chǔ)區(qū)
永久存儲(chǔ)區(qū)是一個(gè)常駐內(nèi)存區(qū)域,用于存放JDK自身所攜帶的Class,Interface的元數(shù)據(jù),也就是說它存儲(chǔ)的是運(yùn)行環(huán)境必須的類信息,被裝載進(jìn)此區(qū)域的數(shù)據(jù)是不會(huì)被垃圾回收器回收掉的,關(guān)閉JVM才會(huì)釋放此區(qū)域所占用的內(nèi)存。
Young Generation Space?新生區(qū)
新生區(qū)是類的誕生、成長(zhǎng)、消亡的區(qū)域,一個(gè)類在這里產(chǎn)生,應(yīng)用,最后被垃圾回收器收集,結(jié)束生命。新生區(qū)又分為兩部分:伊甸區(qū)(Eden space)和幸存者區(qū)(Survivor pace),所有的類都是在伊甸區(qū)被new出來的。幸存區(qū)有兩個(gè):?0區(qū)(Survivor 0 space)和1區(qū)(Survivor 1 space)。當(dāng)伊甸園的空間用完時(shí),程序又需要?jiǎng)?chuàng)建對(duì)象,JVM的垃圾回收器將對(duì)伊甸園區(qū)進(jìn)行垃圾回收,將伊甸園區(qū)中的不再被其他對(duì)象所引用的對(duì)象進(jìn)行銷毀。然后將伊甸園中的剩余對(duì)象移動(dòng)到幸存0區(qū)。若幸存0區(qū)也滿了,再對(duì)該區(qū)進(jìn)行垃圾回收,然后移動(dòng)到1區(qū)。那如果1區(qū)也滿了呢?再移動(dòng)到養(yǎng)老區(qū)。
Tenure generation space養(yǎng)老區(qū)
養(yǎng)老區(qū)用于保存從新生區(qū)篩選出來的JAVA對(duì)象,一般池對(duì)象都在這個(gè)區(qū)域活躍。???三個(gè)區(qū)的示意圖如下:
?q??Method Area?方法區(qū)
方法區(qū)是被所有線程共享,該區(qū)域保存所有字段和方法字節(jié)碼,以及一些特殊方法如構(gòu)造函數(shù),接口代碼也在此定義。
q??PC Register?程序計(jì)數(shù)器
每個(gè)線程都有一個(gè)程序計(jì)數(shù)器,就是一個(gè)指針,指向方法區(qū)中的方法字節(jié)碼,由執(zhí)行引擎讀取下一條指令。
q??Native Method Stack?本地方法棧
來自jameswxx 樵夫后院 的博客
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)總結(jié)
- 上一篇: java线程安全总结
- 下一篇: JVM相关问题