一 概要(本文主要參考深入淺出jvm)
1.什么是jvm?
2.jvm是如何分配內(nèi)存的这弧?
3.jvm是如何保證垃圾正確回收的娃闲?
4.如何監(jiān)控和優(yōu)化gc?
什么是jvm(Java Virtual Machine)匾浪,jvm是jre的一部分皇帮,大家都知道jre是java運(yùn)行環(huán)境由jvm和javaapi組成。jvm通過加載和編譯java文件并通過javaApi進(jìn)行執(zhí)行蛋辈。
既然java宣稱一次編譯到處執(zhí)行属拾,那么它怎么實(shí)現(xiàn)的呢将谊?
我們先來看一下java加載順序
由上圖我們可以看出,不論在什么環(huán)境下只要有對(duì)應(yīng)的jvm就可以運(yùn)行渐白。比如windows尊浓,linux下的虛擬機(jī)都可以運(yùn)行class文件
下面我們來看一下jvm的組成
1.程序計(jì)數(shù)器
? ? 程序計(jì)數(shù)器是一塊較小的內(nèi)存空間,字節(jié)碼解釋器在工作時(shí)就是通過這個(gè)計(jì)數(shù)器的值來選取下一次需要執(zhí)行的字節(jié)碼指令纯衍。
分支栋齿,異常處理,循環(huán)等都需要依賴程序計(jì)數(shù)器來完成襟诸。
程序計(jì)數(shù)器是私有的
為什么呢瓦堵?因?yàn)閖ava虛擬機(jī)的多線程是通過線程間的切換,在任何一個(gè)時(shí)刻一個(gè)處理器只會(huì)處理一條線程中一條指令歌亲。所以為了保證線程切換后能正確執(zhí)行菇用,每條線程都需要一個(gè)單獨(dú)的程序計(jì)數(shù)器。因此小程序計(jì)數(shù)器是私有的陷揪。
2.虛擬機(jī)棧
java虛擬機(jī)棧也是私有的惋鸥,生命周期與線程相同虛擬機(jī)棧描述的是Java?方法執(zhí)行的內(nèi)存模型:每個(gè)方法被執(zhí)行的時(shí)候都會(huì)同時(shí)創(chuàng)建一個(gè)棧用于存儲(chǔ)局部變量表、操作棧鹅龄、動(dòng)態(tài)鏈接揩慕、方法出口等信息。每一個(gè)方法被調(diào)用直至執(zhí)行完成的過程扮休,就對(duì)應(yīng)著一個(gè)棧幀在虛擬機(jī)棧中從入棧到出棧的過程迎卤。
虛擬機(jī)棧中的局部變量表
局部變量表存放了編譯期可知的各種基本數(shù)據(jù)類型(boolean、byte玷坠、char蜗搔、short、int八堡、float樟凄、long、double)兄渺、對(duì)象引用(reference?類型缝龄,不等同于對(duì)象本身,根據(jù)不同的虛擬機(jī)實(shí)現(xiàn)挂谍,它可能是一個(gè)指向?qū)ο笃鹗嫉刂返囊弥羔樖迦溃部赡苤赶蛞粋€(gè)代表對(duì)象的句柄或者其他與此對(duì)象相關(guān)的位置)和returnAddress?類型(指向了一條字節(jié)碼指令的地址)。其中64?位長度的long?和double?類型的數(shù)據(jù)會(huì)占用2?個(gè)局部變量空間(Slot)口叙,其余的數(shù)據(jù)類型只占用1?個(gè)炼绘。局部變量表所需的內(nèi)存空間在編譯期間完成分配,當(dāng)進(jìn)入一個(gè)方法時(shí)妄田,這個(gè)方法需要在幀中分配多大的局部變量空間是完全確定的俺亮,在方法運(yùn)行期間不會(huì)改變局部變量表的大小驮捍。在Java?虛擬機(jī)規(guī)范中,對(duì)這個(gè)區(qū)域規(guī)定了兩種異常狀況:如果線程請(qǐng)求的棧深度大于虛擬機(jī)所允許的深度脚曾,將拋出StackOverflowError?異常东且;如果虛擬機(jī)棧可以動(dòng)態(tài)擴(kuò)展(當(dāng)前大部分的Java?虛擬機(jī)都可動(dòng)態(tài)擴(kuò)展斟珊,只不過Java?虛擬機(jī)規(guī)范中也允許固定長度的虛擬機(jī)棧)苇倡,當(dāng)擴(kuò)展時(shí)無法申請(qǐng)到足夠的內(nèi)存時(shí)會(huì)拋出OutOfMemoryError?異常。
3.本地方法棧
本地方法棧與虛擬機(jī)棧所發(fā)揮的作用是非常相似的囤踩,區(qū)別就是虛擬機(jī)棧執(zhí)行的是java方法旨椒,而本地方法棧執(zhí)行的是native方法
4.java堆
堆內(nèi)存是Java虛擬機(jī)中共享的最大的一塊內(nèi)存,java虛擬機(jī)中啟動(dòng)時(shí)創(chuàng)建堵漱。該內(nèi)存存放的是java實(shí)例對(duì)象综慎。所有的實(shí)例對(duì)象和數(shù)組都在這一塊分配。堆內(nèi)存是jvm虛擬機(jī)垃圾回收的主要區(qū)域勤庐,因此也成為gc堆示惊。
5.方法區(qū)
方法區(qū)與堆內(nèi)存一樣也是共享的一塊區(qū)域,它主要存放已被虛擬機(jī)加載的類信息愉镰,常量米罚,靜態(tài)變量,即時(shí)編譯器編譯后的代碼等數(shù)據(jù)丈探。根據(jù)Java?虛擬機(jī)規(guī)范的規(guī)定录择,當(dāng)方法區(qū)無法滿足內(nèi)存分配需求時(shí),將拋出OutOfMemoryError?異常碗降。
二 java虛擬機(jī)中對(duì)象的訪問及存放
舉個(gè)實(shí)例Student stu=new Student();
這份代碼中Student stu是一個(gè)引用變量所以存放在java虛擬機(jī)棧上隘竭,new Student()是一個(gè)實(shí)例對(duì)象存放在java堆上。另外讼渊,在Java?堆中還必須包含能查找到此對(duì)象類型數(shù)據(jù)(如對(duì)象類型动看、父類、實(shí)現(xiàn)的接口爪幻、方法等)的地址信息菱皆,這些類型數(shù)據(jù)則存儲(chǔ)在方法區(qū)中。
由于reference?類型在Java?虛擬機(jī)規(guī)范里面只規(guī)定了一個(gè)指向?qū)ο蟮囊冒じ澹]有定義這個(gè)引用應(yīng)該通過哪種方式去定位仇轻,以及訪問到Java?堆中的對(duì)象的具體位置,因此不同虛擬機(jī)實(shí)現(xiàn)的對(duì)象訪問方式會(huì)有所不同叶组,主流的訪問方式有兩種:使用句柄和直接指針。如果使用句柄訪問方式Java?堆中將會(huì)劃分出一塊內(nèi)存來作為句柄池历造,reference中存儲(chǔ)的就是對(duì)象的句柄地址甩十,而句柄中包含了對(duì)象實(shí)例數(shù)據(jù)和類型數(shù)據(jù)各自的具體地址信息船庇,如下圖所示。
指針方式
Java?堆對(duì)象的布局中就必須考慮如何放置訪問類型
?這兩種對(duì)象的訪問方式各有優(yōu)勢侣监,使用句柄訪問方式的最大好處就是reference?中存儲(chǔ)的是穩(wěn)定的句柄地址鸭轮,在對(duì)象被移動(dòng)(垃圾收集時(shí)移動(dòng)對(duì)象是非常普遍的行為)時(shí)只會(huì)改變句柄中的實(shí)例數(shù)據(jù)指針,而引用對(duì)象本身不需要被修改橄霉。使用直接指針訪問方式的最大好處就是速度更快窃爷,它節(jié)省了一次指針定位的時(shí)間開銷,由于對(duì)象的訪問在Java?中非常頻繁姓蜂,因此這類開銷積少成多后也是一項(xiàng)非嘲蠢澹可觀的執(zhí)行成本。