這是《深入理解Java虛擬機(jī)》的讀書筆記。
1、Java技術(shù)體系
- Java程序設(shè)計(jì)語言
- 各硬件平臺上的Java虛擬機(jī)
- Class文件格式
- Java API類庫
- 來自商業(yè)機(jī)構(gòu)或者開源社區(qū)的第三方Java類庫
2、Java運(yùn)行時數(shù)據(jù)區(qū)
- 程序計(jì)數(shù)器:是一塊較小的內(nèi)存空間,是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號指示器。唯一一個沒有OOM的內(nèi)存區(qū)域。
- Java虛擬機(jī)棧:生命周期與線程相同。描述的是Java方法執(zhí)行的內(nèi)存模型:每個方法執(zhí)行時都會創(chuàng)建一個棧幀,用于存儲局部變量表、操作棧、動態(tài)鏈接、方法出口等信息。一個方法被調(diào)用執(zhí)行的過程就是棧幀在虛擬機(jī)棧中從入棧到出棧的過程。
成員變量:
* 成員變量定義在類中,在整個類中都可以被訪問。
* 成員變量隨著對象的建立而建立,隨著對象的消失而消失,存在于對象所在的堆內(nèi)存中。
* 成員變量有默認(rèn)初始化值。
局部變量:
* 局部變量只定義在局部范圍內(nèi),如:函數(shù)內(nèi),語句內(nèi)等,只在所屬的區(qū)域有效。
* 局部變量存在于棧內(nèi)存中,作用的范圍結(jié)束,變量空間會自動釋放。
* 局部變量沒有默認(rèn)初始化值
本地方法棧:和java虛擬機(jī)棧作用類似,只不過java虛擬機(jī)棧為虛擬機(jī)執(zhí)行java方法服務(wù),而本地方法棧為虛擬機(jī)使用到的Native方法服務(wù)。有些虛擬機(jī)把java虛擬機(jī)棧和本地方法棧合二為一了。
Java堆:幾乎所有的對象實(shí)例都在這里分配內(nèi)存。是垃圾收集器管理的主要區(qū)域,因此也叫“GC堆”,還好沒叫垃圾堆。雖然是線程共享,也可能劃分多個線程私有的分配緩沖區(qū)(TLAB)。從垃圾收集器算法角度可以把堆分成:新生代和老年代;在細(xì)點(diǎn)可以有Eden空間、From Survivor空間、To Survivor空間等。當(dāng)前虛擬機(jī)堆都是可擴(kuò)展的,可通過-Xmx最大值和-Xms最小值控制,設(shè)置成一樣可避免堆自動擴(kuò)展。
方法區(qū):用于存儲已被虛擬機(jī)加載的類信息、常量、靜態(tài)變量、即時編譯的代碼等數(shù)據(jù)。
程序計(jì)數(shù)器、虛擬機(jī)棧、本地方法棧三個區(qū)域隨線程生而生,雖線程滅而滅。其中棧幀隨著方法的進(jìn)入和退出而有條不紊的執(zhí)行著入棧和出棧操作。每個棧幀分配多少內(nèi)存在類結(jié)構(gòu)確定下來時就已知了,這幾個區(qū)域內(nèi)存分配和回收都具有穩(wěn)定性,不需要過多考慮回收問題,線程滅就自動回收了。java堆和方法區(qū)的內(nèi)存都是動態(tài)分配和回收的。方法區(qū)進(jìn)行垃圾收集“性價比”比較低,主要還是管理Java堆。
對象訪問
主流的訪問方式有兩種:句柄和直接指針。在各種語言和框架中這兩種訪問方式都很常見,各有千秋。
句柄訪問
java堆中劃出一塊內(nèi)存作為句柄池,句柄中包含對象實(shí)例數(shù)據(jù)和類型數(shù)據(jù)各自的具體地址信息。最大的好處就是reference中存儲的是穩(wěn)定的句柄地址,在對象被移動的時候只改變句柄的實(shí)例數(shù)據(jù)指針。(垃圾收集時移動對象是非常常見的現(xiàn)象)
直接指針
Java堆中的對象布局必須考慮如何防止類型數(shù)據(jù)地址。reference中存儲的直接就是對象地址。最大的好處就是速度快,節(jié)省一次指針定位的時間開銷。
再談引用
引用:當(dāng)reference類型的數(shù)據(jù)中存儲的數(shù)值代表的是另一塊內(nèi)存的起始地址,就稱其為引用。引用有如下4中類型:
強(qiáng)引用:只要強(qiáng)引用還存在,GC永遠(yuǎn)不會回收被引用的對象;
軟引用(SoftReference):描述一些還有用,但不是必須的對象。對于只有軟引用的對象,內(nèi)存不足時,在OOM之前會被回收。
弱引用(WeakReference):描述的也是非必須的對象,但程度比軟引用弱。只能存活到下次垃圾收集器工作之前,也就是說只要垃圾收集器工作,這種引用的對象都會被回收,不管當(dāng)前內(nèi)存是否足夠。
虛引用(PhantomReference):又稱幽靈引用或者幻影引用。它是最弱的一種引用關(guān)系。虛引用不會影響對象的生存時間,通過虛引用也無法獲取對象實(shí)例。設(shè)置虛引用的唯一目的就是在對象對收集器回收時收到一個系統(tǒng)的通知。
3、垃圾收集器
確定對象是否已死
引用計(jì)數(shù)器:給對象添加一個引用計(jì)數(shù)器,每當(dāng)有地方引用對象時,計(jì)數(shù)器就加1,當(dāng)引用失效時,計(jì)數(shù)器就減1。當(dāng)計(jì)數(shù)器等于0時,對象就不再可能被使用了。
優(yōu)點(diǎn):實(shí)現(xiàn)簡單,判定效率高。
缺點(diǎn):很難解決對象間循環(huán)引用的問題。根搜索算法:通過一系列名為“GC Roots”的對象作為起點(diǎn)向下搜索,搜索所走過的路徑成為引用鏈。當(dāng)一個對象到GC Roots沒有任何引用鏈時,則證明這個對象不再可能被使用了。
在Java中可作為GC Roots的對象有:
* 虛擬機(jī)棧中引用的對象(棧幀中本地變量表)
* 方法區(qū)中的類靜態(tài)屬性引用的對象
* 方法區(qū)中的常量引用的對象
* 本地方法棧幀中JNI引用的對象
收集器判斷對象不可達(dá)后,會檢查對象的finalize()方法是否被執(zhí)行過,如果沒有就先執(zhí)行finalize()。一個對象的finalize()只會被執(zhí)行一次,如果一個對象在finalize()方法中被救活了,即重新被引用了,當(dāng)它再次不可達(dá)時不會執(zhí)行finalize()方法了。
垃圾收集算法
- 標(biāo)記-清除算法(Mark-Sweep):顧名思義收集分為“標(biāo)記”和“清除”兩個階段。標(biāo)記就是上面介紹的標(biāo)記對象死亡的過程。
優(yōu)點(diǎn):這是最基礎(chǔ)的收集算法,其他的算法都是它的基礎(chǔ)上演變來的。
缺點(diǎn):1、效率問題,標(biāo)記和清除效率都不高;2、空間問題,標(biāo)記清除后產(chǎn)生大量的不連續(xù)的內(nèi)存碎片。
- 復(fù)制算法(Copying):將內(nèi)存分成大小相等的A、B兩塊,每次只使用其中一塊,比如A,當(dāng)A使用完了,就把還存活的對象拷到B上,然后把A空間一次清掉。
優(yōu)點(diǎn):實(shí)現(xiàn)簡單、運(yùn)行效率高。
缺點(diǎn):可使用的內(nèi)存縮小為原來的一半
改進(jìn):IBM研究表面,新生代中98%的對象都是朝生夕死,所以并不需要1:1來劃分。所以分成一塊較大的Eden空間和a、b兩塊較小的Survivor空間(Eden:Survivor = 8:1)。每次使用Eden和一塊Survivor空間,比如Survivor-a,用完了就把存活的對象拷到Survivor-b上,然后清除Eden和Survivor-a,然后再使用Eden和Survivor-b。每次浪費(fèi)10%的空間,如果存活對象大于10%就把多出來的對象放到給新生代擔(dān)保的老年代。
目前商業(yè)虛擬機(jī)都采用改進(jìn)后的復(fù)制算法回收新生代。
標(biāo)記-整理算法(Mark-Compact):標(biāo)記還是對象標(biāo)記的那個標(biāo)記。整理是把存活的對象移動到內(nèi)存的一端,然后清理掉端邊界以外的內(nèi)存。
分代收集算法(Generational Collection):這個算法沒有新思想。只是根據(jù)對象存活周期將內(nèi)存劃分成新生代和老年代兩塊。然后不同的年代采用不同收集算法,新生代用復(fù)制算法,老年代因?yàn)闆]有額外空間給它內(nèi)存擔(dān)保,而且對象存活時間長,那就用標(biāo)記-整理或者標(biāo)記-清除。
4、類加載
Class文件需要被加載到虛擬機(jī)中才能被運(yùn)行和使用。生命周期包括:加載(Loading)、驗(yàn)證(Verification)、準(zhǔn)備(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸載(Unloading)七個階段。
類加載器
虛擬機(jī)中類的唯一性由類本身和它的加載器決定。
雙親委派模型的父子關(guān)系不是繼承,而是組合關(guān)系。
雙親委派模型的工作工程:類加載器收到類加載請求時,首先不會嘗試自己加載這個類,而是把這個請求委派給父類加載器去完成,每一層加載器都是如此,所以所有的加載請求都會傳遞到啟動類加載器,只有父加載器反饋?zhàn)约簾o法完成這個加載請求時(它的搜索范圍中沒有找到所需的類),子加載器才會嘗試自己去加載。