Java虛擬機

注:本文參考自《深入理解Java虛擬機:JVM高級特性與最佳實踐》及其它優(yōu)秀的博客,在此表示對這些作者們的感謝。

一、Java虛擬機運行時數(shù)據(jù)區(qū)

Java虛擬機在執(zhí)行Java程序的過程中會把它所管理的內(nèi)存劃分為若干個不同的數(shù)據(jù)區(qū)域。如下圖:

Java虛擬機運行時數(shù)據(jù)區(qū)

1、棧

Java虛擬機棧是線程私有的(JVM為每個新創(chuàng)建的線程都分配一個棧),它的生命周期與線程相同。虛擬機棧描述的是Java方法執(zhí)行的內(nèi)存模型:每個方法在執(zhí)行的同時都會創(chuàng)建一個棧幀(Stack Frame)用來存儲局部變量表、操作數(shù)棧、動態(tài)鏈接、方法出口等信息。每一個方法從調(diào)用到執(zhí)行完成的過程,就對應(yīng)著一個棧幀在虛擬機棧中入棧到出棧的過程。
??局部變量表存儲了編譯期可知的各種基本類型(boolean、byte、char、short、int、float、long、double)、對象引用(reference類型)。局部變量表所需的內(nèi)存空間在編譯期間完成確定,當(dāng)進入一個方法時,這個方法需要在幀中分配多大的局部變量空間是完全確定的,在方法運行期間不會改變局部變量表的大小

2、堆

Java堆是Java虛擬機所管理的內(nèi)存中最大的一塊。Java堆是被所有線程共享的一塊內(nèi)存區(qū)域,在虛擬機啟動時創(chuàng)建。所有的對象實例及數(shù)組都要在堆上分配。
??Java堆是垃圾收集器管理的主要區(qū)域,也被稱為GC堆。由于現(xiàn)在的垃圾收集器基本都采用分代收集算法,因此堆還可以細(xì)分為:新生代和老年代;再細(xì)分一點有Eden空間、From Survivor空間、To Survivor空間等。
??Java堆可以處于物理上不連續(xù)的內(nèi)存空間中,只要邏輯上連續(xù)即可。

3、方法區(qū)

存儲已被虛擬機加載的類信息、常量、靜態(tài)變量、即時編譯器編譯后的代碼等數(shù)據(jù)。
??Class文件常量池:存儲類的版本,字段,方法,接口等描述信息
??運行時常量池:存放編譯期生成的各種字面量和符號引用。

4、對象的創(chuàng)建

4.1 檢查常量池完成類加載:虛擬機遇到一條new指令時,首先將去檢查這個指令的參數(shù)是否能在常量池中定位到一個類的符號引用,并檢查這個符號引用代表的類是否已經(jīng)被加載、解析和初始化過。如果沒有,那必須先執(zhí)行相應(yīng)類的加載過程。
??4.2 在堆中為新生對象分配內(nèi)存:對象所需要的大小在類加載完成后便可完全確定,為對象分配空間的任務(wù)等同于把一塊確定大小的內(nèi)存從Java堆中劃分出來。
??4.3 虛擬機將新分配的內(nèi)存空間都初始化為零值(不包括對象頭)
??4.4 虛擬機對對象進行設(shè)置:虛擬機設(shè)置這個對象是哪個類的實例、如何才能找到類的元數(shù)據(jù)信息、對象的哈希碼、對象的GC分代年齡等信息。
??4.5 執(zhí)行<init>方法,完成初始化

示例一:字符串創(chuàng)建

字符串對象的引用存儲在棧中,編譯期已創(chuàng)建好的(雙引號定義的)則存儲在常量池,運行時才new出來的則存儲在堆中。對于equals相等的字符串,在常量池中只有一份,在堆中有多份
??下圖中橙色的線:new一個字符串時,會先去常量池中檢查是否存在此對象,如果沒有則在常量區(qū)創(chuàng)建此字符串對象,再在堆中創(chuàng)建一個該對象的拷貝對象。因此,String s = new String("xyz"); 產(chǎn)生幾個對象?一個或兩個,如果常量池中原來沒有"xyz",就是兩個。

String s1 = "china";
String s2 = "china";
String s3 = "china";
String ss1 = new String("china");
String ss2 = new String("china");
String ss3 = new String("china");
字符串創(chuàng)建

示例二:變量和常量

變量和引用存儲在棧中,常量存儲在常量池中。局部變量是方法或語句塊內(nèi)塊定義的變量(包括形式參數(shù)),存儲在棧中,隨著方法的消失而消失;成員變量是方法外部的變量,存儲在堆中該對象里,由垃圾回收器負(fù)責(zé)回收。
??棧中i1、i2、i3都指向9(出于壓縮空間提高利用率的考慮),如果令i2=7,會在棧里生成7再令i2指向7。(區(qū)別于對象的引用:多個引用指向同一個對象,修改其中一個引用指向的對象,則其它引用指向的對象發(fā)生修改)

int i1 = 9;
int i2 = 9;
int i3 = 9; 
public static final int INT1 = 9;
public static final int INT2 = 9;
public static final int INT3 = 9;
變量和常量

二、垃圾回收機制

1、對象已死?

1.1 引用計數(shù)法(JVM不用)
1.2 可達(dá)性分析算法

2、垃圾回收算法

2.1 標(biāo)記-清除算法
??標(biāo)記-清除:首先標(biāo)記出所有需要回收的對象,在標(biāo)記完成后統(tǒng)一回收所有被標(biāo)記的對象。
??缺點:1、標(biāo)記和清除兩個階段的效率都不夠高; 2、標(biāo)記清除后會產(chǎn)生大量的不連續(xù)內(nèi)存碎片。

標(biāo)記-清除算法示意圖

2.2 復(fù)制算法
??復(fù)制算法:將可用內(nèi)存劃分為大小相等的兩塊,每次只使用其中的一塊。當(dāng)這一塊的內(nèi)存用完了,就將還存活著的復(fù)制到另外一塊上面,然后再把已使用過的內(nèi)存空間一次清理掉。
??使用場景:這種垃圾收集算法常用于新生代。 IBM研究表明,新生代中的對象98%是“朝生夕死”的。HotSpot默認(rèn)把內(nèi)存區(qū)域分為一個Eden(80%)、兩個Survivor(10%),每次使用Eden和Survivor其中的一塊。

復(fù)制算法示意圖

2.3 標(biāo)記-整理算法
??標(biāo)記-整理算法:標(biāo)記過程仍然與標(biāo)記-清除算法一樣,但后續(xù)步驟不是直接對可回收對象進行清理,而是讓所有存活的對象都向一端移動,然后直接清理掉端邊界以外的內(nèi)存。

標(biāo)記-整理算法示意圖

2.4 分代收集算法
??分代收集算法:根據(jù)對象存活的時間不同把內(nèi)存(Java堆)劃分為新生代和老年代。
??策略:在新生代中,每次垃圾收集時都發(fā)現(xiàn)有大批的對象死去,只有少量存活,選用復(fù)制算法,只需復(fù)制少量存活對象就可以完成收集;老年代中因為對象的存活率高、沒有額外的空間對它進行擔(dān)保,必須采用標(biāo)記-清理算法或者標(biāo)記整理算法。

3、垃圾回收器

3.1 Serial & Serial Old
3.2 Paraller Scavenge & Paraller Old
3.3 ParNew & CMS(G1)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容