譯者前言
本文介紹了IBM JDK 1.2.2到1.4.1 SR1版本垃圾收集原理,虛擬機(jī)內(nèi)部?jī)?nèi)存分配及管理的機(jī)制。根據(jù)IBM的說(shuō)明,本文檔也適合JDK 1.4.2。總體感覺(jué)翻譯這篇文檔比Sun HotSpot虛擬機(jī)的內(nèi)存管理機(jī)制的那一篇要更加吃力一些。文檔中介紹了很多的細(xì)節(jié),也有較多的陌生的詞。在文檔的最后附有中英文詞匯對(duì)照表。如果有錯(cuò)誤之處,希望大家踴躍指出。如果你的英語(yǔ)水平可以的話,建議去讀原文。
了解虛擬機(jī)內(nèi)部?jī)?nèi)存管理以及垃圾收集的機(jī)制,可能有助于你調(diào)優(yōu)虛擬機(jī)。另外,翻譯這篇文章主要的原因是IBM JVM 和HotSpot JVM的有很大的區(qū)別,基本上是兩套差異較大的思路出來(lái)的產(chǎn)品。HotSpot是分代的,IBM JVM是不分的。但是根據(jù)IBM的文檔,在1.5以及后續(xù)的版本中,提供了分代收集的策略。JRockit可以同時(shí)支持分代和不分代的兩種策略。看來(lái)大家都在互相取長(zhǎng)補(bǔ)短啊。
原文出自
http://www-128.ibm.com/developerworks/java/jdk/diagnosis/142.html
Garbage Collection and Storage Allocation techniques
轉(zhuǎn)載請(qǐng)注明出處(http://www.tkk7.com/yoda/),謝謝!
1 簡(jiǎn)介
本文檔描述從1.2.2到1.4.1 SR1的存儲(chǔ)組件(ST)功能。(譯者注:根據(jù)IBM的說(shuō)明,本文檔也適合JDK 1.4.2)
存儲(chǔ)組件(Storage Component)用來(lái)分配堆中的存儲(chǔ)空間,這些存儲(chǔ)空間定義了對(duì)象、數(shù)組以及類。每個(gè)對(duì)象在存儲(chǔ)空間中占有一部分存儲(chǔ),如果從虛擬機(jī)活動(dòng)狀態(tài)中存在到這個(gè)對(duì)象的引用(指針),這個(gè)對(duì)象就是可到達(dá)的;當(dāng)虛擬機(jī)活動(dòng)狀態(tài)不再存在到這個(gè)對(duì)象的引用,這個(gè)對(duì)象就被視為垃圾,他占用的存儲(chǔ)空間可以被重新使用。當(dāng)申請(qǐng)重用發(fā)生時(shí),垃圾收集器必須進(jìn)行一些清理工作,以確保該對(duì)象所關(guān)聯(lián)的監(jiān)視器能夠被釋放回對(duì)應(yīng)的監(jiān)視器池中。存儲(chǔ)組件并不是等同對(duì)待所有的對(duì)象,比如類對(duì)象以及線程對(duì)象是在堆的一個(gè)特殊空間(Pinned Cluster,固化簇)內(nèi)進(jìn)行分配的;在追蹤整個(gè)堆的時(shí)候,引用對(duì)象和其派生對(duì)象也是被特殊處理的。在4.4章"引用對(duì)象"中對(duì)于這種特殊情況有詳細(xì)的闡述。
1.1 對(duì)象分配
對(duì)象分配是由對(duì)于某個(gè)分配接口的調(diào)用來(lái)驅(qū)動(dòng)的,例如stCacheAlloc, stAllocObject, stAllocArray, stAllocClass。調(diào)用這些接口會(huì)在堆上分配一塊空間,但是調(diào)用時(shí)的參數(shù)有所不同。stCacheAlloc函數(shù)是為小對(duì)象的高效分配所設(shè)計(jì)的,每個(gè)線程會(huì)預(yù)先從堆中申請(qǐng)一塊獨(dú)享的空間,叫做線程本地堆(Thread Local Heap),簡(jiǎn)稱TLH。小對(duì)象在這個(gè)空間內(nèi)直接進(jìn)行分配。一個(gè)新的對(duì)象從線程本地堆的底部開始分配,由于無(wú)需獲取堆鎖,所以這種分配動(dòng)作是非常高效的。如果對(duì)象較小(當(dāng)前上限是512字節(jié)),那么即使使用stAllocObject和stAllocArray接口,對(duì)象也會(huì)分配在線程本地堆。
1.2 可到達(dá)對(duì)象
線程執(zhí)行棧、類內(nèi)部的靜態(tài)對(duì)象以及本地和全局的JNI引用共同構(gòu)成了虛擬機(jī)的活動(dòng)狀態(tài)。虛擬機(jī)內(nèi)部調(diào)用的函數(shù)會(huì)導(dǎo)致在C執(zhí)行棧上生成一個(gè)幀,這些信息用來(lái)尋找根對(duì)象,從根對(duì)象出發(fā)來(lái)尋找被引用的對(duì)象。這個(gè)過(guò)程會(huì)一直重復(fù)直到找到全部可到達(dá)對(duì)象。
1.3 垃圾收集
當(dāng)堆內(nèi)存空間不足,導(dǎo)致虛擬機(jī)內(nèi)存分配失敗時(shí),就會(huì)發(fā)生垃圾收集。垃圾收集的第一步工作就是找到堆中全部的垃圾,這個(gè)工作可以由任何線程的內(nèi)存分配失敗激活,也可以由顯式調(diào)用System.gc()函數(shù)激活。首先,要獲取垃圾收集所需的全部鎖,這可以保證對(duì)于當(dāng)時(shí)擁有臨界鎖的線程不會(huì)被掛起。通過(guò)執(zhí)行管理接口(execution manager, XM)來(lái)掛起其他線程,以確保對(duì)于調(diào)用線程,其他線程的掛起狀態(tài)是可進(jìn)入的,這個(gè)狀態(tài)包括在掛起時(shí)刻的從頂?shù)降椎膱?zhí)行棧以及寄存器狀態(tài),以用來(lái)追蹤對(duì)象引用。在此之后,垃圾收集才可以開始工作,包含3個(gè)階段:
· 標(biāo)識(shí)
· 清理
· 壓縮(可選的)
1.3.1 標(biāo)識(shí)階段
在標(biāo)識(shí)階段,所有的被虛擬機(jī)活動(dòng)狀態(tài)引用,或者靜態(tài)的,或者固化字符串以及被JNI引用的對(duì)象都被標(biāo)識(shí)。這個(gè)動(dòng)作創(chuàng)建了JVM引用的根對(duì)象,這些根對(duì)象可能會(huì)依次引用其他對(duì)象,因此,標(biāo)識(shí)階段的第二部分工作就是從根對(duì)象出發(fā),掃描其他被引用對(duì)象。這兩步工作產(chǎn)生一個(gè)活動(dòng)對(duì)象集合。
分配集合(allocbits)中的每個(gè)比特位標(biāo)識(shí)堆中的一個(gè)8字節(jié)段,一旦分配了一個(gè)對(duì)象,分配集合中的對(duì)應(yīng)比特位會(huì)被標(biāo)識(shí)。垃圾收集器開始追蹤棧時(shí),首先比較指向堆底和堆頂?shù)闹羔槪_保指針指向的是8字節(jié)邊界的對(duì)象,然后對(duì)分配集合中對(duì)應(yīng)的比特位進(jìn)行標(biāo)識(shí),表示該指針指向一個(gè)活動(dòng)對(duì)象。然后在標(biāo)識(shí)集合(markbits)中對(duì)對(duì)應(yīng)的比特位進(jìn)行標(biāo)識(shí),表明該對(duì)象處于被引用狀態(tài)。
最后,垃圾收集器掃描對(duì)象的域字段查找被這個(gè)對(duì)象引用的其他對(duì)象,這個(gè)掃描過(guò)程是準(zhǔn)確完成的,因?yàn)榉椒ㄖ羔槾鎯?chǔ)在第一個(gè)字單元,垃圾收集器能夠知道對(duì)象的類型。在對(duì)象鏈接時(shí)(對(duì)象第一個(gè)實(shí)例創(chuàng)建之前),類裝載器會(huì)創(chuàng)建一個(gè)偏移集合,這個(gè)偏移集合中記錄了對(duì)象中引用其他對(duì)象的字段偏移位置,垃圾收集器通過(guò)訪問(wèn)這個(gè)偏移集合找到對(duì)應(yīng)的域及其引用的其他對(duì)象。
1.3.2 清理階段
標(biāo)識(shí)階段之后,在標(biāo)識(shí)集合中包含了堆中所有可到達(dá)對(duì)象的標(biāo)志。標(biāo)識(shí)集合必須是分配集合的一個(gè)子集,清理階段的工作就是找到這兩個(gè)集合之間的差集,也就是在那些已經(jīng)分配但是不再被引用的對(duì)象。
最起初,清理階段就是從堆底開始掃描,依次訪問(wèn)堆中的每個(gè)對(duì)象。對(duì)象的長(zhǎng)度存儲(chǔ)在一個(gè)字單元中,對(duì)于每個(gè)對(duì)象,垃圾收集器檢測(cè)對(duì)應(yīng)的分配標(biāo)識(shí)位和標(biāo)識(shí)標(biāo)識(shí)位以定位垃圾。
現(xiàn)在,使用bitsweep技術(shù)無(wú)需掃描整個(gè)堆,這樣就避免了頁(yè)交換的額外消耗。bitsweep技術(shù)直接在標(biāo)識(shí)集合中尋找長(zhǎng)的連續(xù)的0(未被標(biāo)識(shí)的對(duì)象),這段長(zhǎng)的連續(xù)的0可能表示一段空閑空間。找到這樣的序列之后,垃圾收集器檢測(cè)序列開始前的對(duì)象的長(zhǎng)度,以檢測(cè)可以被釋放的空閑空間的大小。
1.3.3 壓縮階段
垃圾收集器將垃圾從堆中移除之后,將剩下的對(duì)象向一側(cè)壓縮排列,以便移除這些對(duì)象之間的空閑空間。由于壓縮動(dòng)作消耗很大,所以應(yīng)該盡量避免。在4.3.1章對(duì)于如何避免壓縮有詳細(xì)的闡述。
壓縮是一個(gè)非常復(fù)雜的過(guò)程,因?yàn)榫浔呀?jīng)不再存在于虛擬機(jī)中了。如果垃圾收集器移動(dòng)了某個(gè)對(duì)象,那么需要修改所有指向這個(gè)對(duì)象的引用。如果有來(lái)自棧的引用,那么就無(wú)法確定是這個(gè)引用確實(shí)是對(duì)象引用(有可能是一個(gè)浮點(diǎn)數(shù)),所以這個(gè)對(duì)象就不能移動(dòng)。這種對(duì)象臨時(shí)固定在他原來(lái)的位置,并且在頭信息中使用對(duì)應(yīng)比特位標(biāo)識(shí)。類似的,在JNI操作的過(guò)程中,從JNI引用的Java對(duì)象也是固化的,無(wú)法移動(dòng),直到JNI操作結(jié)束。利用mptr低三位設(shè)置為0,可移動(dòng)的對(duì)象在兩個(gè)階段內(nèi)被壓縮到一起。這3個(gè)比特位中一個(gè)用來(lái)標(biāo)識(shí)對(duì)象已經(jīng)被清理,注意,清理標(biāo)識(shí)位出現(xiàn)在兩個(gè)地方:link域(即OLINK_IsSwapped)和mptr (GC_FirstSwapped)。這兩種情況,都會(huì)設(shè)置最低的比特位(x01)。
在壓縮階段的最后,所有線程通過(guò)XM恢復(fù)運(yùn)行。
2 數(shù)據(jù)區(qū)域
2.1 一個(gè)對(duì)象
圖表 1 一個(gè)對(duì)象
圖表1是堆中一個(gè)對(duì)象的布局
· size + flags (大小和標(biāo)識(shí))
size + flags在32位架構(gòu)上占4字節(jié),在64位架構(gòu)上占8字節(jié),主要目的是用來(lái)存儲(chǔ)對(duì)象的大小。由于對(duì)象都是從8字節(jié)邊界開始,并且對(duì)象大小可以被8整除,最低3個(gè)比特位不被使用,垃圾收集器用來(lái)作為標(biāo)識(shí)位以標(biāo)識(shí)對(duì)象的不同狀態(tài)。另外,由于對(duì)象大小是有限的,所以最高2個(gè)比特位也用來(lái)作為標(biāo)識(shí)位(mptr也是8字節(jié)邊界的)。
size + flags 中的標(biāo)識(shí)位如下:
§ 第1位有多個(gè)用途。在清理階段作為清理標(biāo)識(shí),在壓縮階段也會(huì)用到它。第1位還是多次固化標(biāo)識(shí)位,用來(lái)標(biāo)識(shí)該對(duì)象被多次固化。在垃圾收集的一個(gè)周期,多次固化標(biāo)識(shí)位被清除,用作其他用途,然后恢復(fù)
§ 第2位是dosed位。如果從棧或者寄存器有到某個(gè)對(duì)象的“引用”,就會(huì)在dosed位進(jìn)行標(biāo)識(shí)。這里“引用”是指在本次垃圾收集周期不能移動(dòng)該對(duì)象,因?yàn)槔占鳠o(wú)法確定該“引用”是一個(gè)真正指向?qū)ο蟮囊眠€是碰巧只是一個(gè)和對(duì)象句柄值相等的整數(shù)
§ 第3位是固化標(biāo)識(shí)位。固化的對(duì)象就是從堆之外有指向這個(gè)對(duì)象的引用,例如線程和類對(duì)象。此類對(duì)象無(wú)法移動(dòng)
§ 32位架構(gòu)的第31位或者64位架構(gòu)的第63位是鎖(flat lock)競(jìng)爭(zhēng)位,被鎖管理模塊(locking, LK)使用
§ 32位架構(gòu)的第32位或者64位架構(gòu)的第64位是哈希標(biāo)識(shí)位,表示一個(gè)對(duì)象已經(jīng)返回了哈希值。因?yàn)閷?duì)象的哈希值就是對(duì)象的地址,如果垃圾收集器移動(dòng)了這個(gè)對(duì)象,需要維護(hù)這個(gè)值
· mptr
mptr槽位在32位架構(gòu)上占4字節(jié),在64位架構(gòu)上占8字節(jié),mptr是8字節(jié)邊界的(譯者注:原文為The mptr slot is grained on an 8-byte boundary, not the size + flags. 不知道如何翻譯才是準(zhǔn)確的),功能為以下兩種之一
§ 如果對(duì)象不是數(shù)組,mptr指向類方法表,垃圾收集器據(jù)此找到類信息。通過(guò)這種方式,垃圾收集器知道對(duì)象是從哪個(gè)類實(shí)例化的。類的方法表和類本身的信息由類加載器組件(class loader, CL)分配,但是不存儲(chǔ)在堆空間
§ 如果對(duì)象是數(shù)組,mptr是數(shù)組內(nèi)條目的計(jì)數(shù)
· locknflags
locknflags槽位在32位架構(gòu)上占4字節(jié),在64位架構(gòu)上占8字節(jié),但是只有低4字節(jié)被使用。主要作用是用來(lái)保存鎖信息。另外還包含3個(gè)標(biāo)識(shí)比特位
§ 第2比特位是數(shù)組標(biāo)識(shí)。如果對(duì)象是數(shù)組,則該標(biāo)識(shí)位設(shè)置為1,mptr槽位保存數(shù)組中對(duì)象的個(gè)數(shù)
§ 第3比特位是哈希和移動(dòng)標(biāo)識(shí)位,如果這個(gè)標(biāo)識(shí)位被設(shè)置為1,表明該對(duì)象被移動(dòng)過(guò),可以在對(duì)象在移動(dòng)之前的位置找到其哈希值
§ (譯者注:在圖表1中并未畫出locknflags槽位,原文如此,筆誤?)
· 對(duì)象數(shù)據(jù)
這里是開始記錄對(duì)象數(shù)據(jù)的位置
size + flags, mptr以及l(fā)ocknflags一起稱為對(duì)象頭信息
2.2 堆
圖表 2 堆
圖表2是堆的示意圖。堆是虛擬機(jī)在初始化時(shí)從操作系統(tǒng)申請(qǐng)的一段連續(xù)的內(nèi)存空間。堆底是堆的開始地址,堆頂是堆的結(jié)束地址,堆上限是堆中當(dāng)前使用部分的結(jié)束地址。堆上限可以擴(kuò)展和收縮。-Xmx參數(shù)限制了從堆底到堆頂?shù)淖畲笾担绻丛O(shè)置,默認(rèn)值如下:
· Xmx
§ Windows: 物理內(nèi)存的一半,最小16MB,最大2GB-1
§ OS/390 和 AIX: 64MB
§ Linux: 物理內(nèi)存的一半,最小16MB,最大512MB-1
· Xms
§ Windows, AIX, Linux: 4MB
§ OS/390: 1MB
2.2.1 設(shè)置堆大小
對(duì)于大多數(shù)應(yīng)用而言,默認(rèn)的設(shè)置即可滿足需求。在運(yùn)行時(shí),堆會(huì)自動(dòng)擴(kuò)展到一個(gè)穩(wěn)定的狀態(tài),保證在任何時(shí)刻堆中活動(dòng)對(duì)象占堆大小的70%,在這種狀態(tài)下,垃圾收集的頻率和暫停時(shí)間都是可以接受的。
對(duì)于某些應(yīng)用而言,默認(rèn)參數(shù)可能無(wú)法保證應(yīng)用的良好運(yùn)行,下面列出可能出現(xiàn)的問(wèn)題以及應(yīng)對(duì)策略。使用verbosegc可以監(jiān)控堆的使用情況。
· 在堆到達(dá)穩(wěn)定狀態(tài)之前,垃圾收集發(fā)生的過(guò)于頻繁
使用verbosegc檢測(cè)堆到達(dá)穩(wěn)定狀態(tài)時(shí)的大小,然后設(shè)置-Xms參數(shù)等于該大小
· 堆已經(jīng)擴(kuò)展到最大限制,但是堆占用率仍然高于70%
增加-Xmx的設(shè)置,以使得堆能夠擴(kuò)展到一個(gè)能夠保證占用率不高于70%的大小。但是需要注意的是,要保證堆的內(nèi)存都是從物理內(nèi)存占用,避免出現(xiàn)頁(yè)交換
· 堆占用率在70%,但是垃圾收集發(fā)生的頻率過(guò)高
修改-Xminf參數(shù)。默認(rèn)是0.3,表示堆要通過(guò)擴(kuò)展保證有30%的空閑空間。例如,設(shè)置該參數(shù)為0.4,會(huì)降低垃圾收集發(fā)生的頻率
· 暫停時(shí)間過(guò)長(zhǎng)
嘗試使用-Xgcpolicy:optavgpause參數(shù)。在堆的占用比增高的情況下,該參數(shù)能夠保證垃圾收集時(shí)間的穩(wěn)定,但是會(huì)帶來(lái)大約5%的吞吐量的下降,具體視應(yīng)用而定
另外,還有一些小提示:
· 確保堆不會(huì)發(fā)生頁(yè)交換(即堆內(nèi)存全部從物理內(nèi)存中獲取)
· 避免使用finalizer。你不能確保finalizer執(zhí)行的時(shí)機(jī),這樣會(huì)帶來(lái)一些問(wèn)題。在verbosegc的輸出中,可以看到是否已經(jīng)執(zhí)行了finalizer。如果確實(shí)需要使用finalizer,需要注意以下三個(gè)關(guān)鍵點(diǎn)
§ 不要在finalizer方法中創(chuàng)建新的對(duì)象
§ 不要依賴finalizer來(lái)釋放一些本地資源
§ 不要在finalizer方法中進(jìn)行長(zhǎng)時(shí)間執(zhí)行的或者阻斷式的動(dòng)作
· 避免壓縮。verbosegc的輸出中可以顯示是否進(jìn)行了壓縮動(dòng)作。通常,壓縮動(dòng)作是由于大塊內(nèi)存的分配引發(fā)的,所以,要分析應(yīng)用中對(duì)于大塊內(nèi)存的需求,比如,一個(gè)大的數(shù)組對(duì)象,可以將其拆分成多個(gè)小片段
2.3 分配集合和標(biāo)識(shí)集合
圖表 3 堆以及分配集合和標(biāo)識(shí)集合
圖表3是堆、分配集合和標(biāo)識(shí)集合的示意圖。這兩個(gè)比特位集合標(biāo)記了堆中對(duì)象的狀態(tài)。因?yàn)槎阎袑?duì)象都是8字節(jié)邊界的,所以每個(gè)比特位對(duì)應(yīng)1個(gè)8字節(jié)段,這兩個(gè)比特位集合的大小為堆大小的1/64。
一旦在堆中分配了一個(gè)對(duì)象,在分配集合中對(duì)應(yīng)對(duì)象開始地址的比特位被設(shè)置為1。分配集合只是標(biāo)識(shí)了對(duì)象被分配,但是無(wú)法得知對(duì)象是否是活動(dòng)的。在垃圾收集的標(biāo)識(shí)階段,標(biāo)識(shí)集合中對(duì)應(yīng)的比特位會(huì)被設(shè)置,以表示對(duì)象是活動(dòng)的。圖表4表示堆中的2個(gè)對(duì)象,分配集合中對(duì)應(yīng)比特位都被設(shè)置為1.
圖表 4 堆中一些對(duì)象
在標(biāo)識(shí)階段,Object2是被引用的,Object1是未被引用的,所以在標(biāo)識(shí)集合中對(duì)應(yīng)
Object2的比特位被設(shè)置為1,在清理階段,Object1會(huì)被垃圾收集。
2.4 系統(tǒng)堆
圖表 5 系統(tǒng)堆
系統(tǒng)堆中包含的對(duì)象是跨越整個(gè)虛擬機(jī)生命周期的對(duì)象,通常這些對(duì)象是系統(tǒng)級(jí)的類對(duì)象、可共享的中間對(duì)象以及應(yīng)用級(jí)對(duì)象。垃圾收集器不會(huì)收集系統(tǒng)堆中的對(duì)象,因?yàn)檫@些對(duì)象在整個(gè)虛擬機(jī)生命周期內(nèi)都是可達(dá)到的,或者是應(yīng)用需要共享的一些對(duì)象。圖表5是系統(tǒng)堆的示意圖。系統(tǒng)堆不是連續(xù)的存儲(chǔ)空間,而是由多個(gè)存儲(chǔ)段構(gòu)成的鏈。系統(tǒng)堆的初始大小在32位架構(gòu)上是128KB,在64位架構(gòu)上是8MB。如果系統(tǒng)堆被對(duì)象充滿,虛擬機(jī)會(huì)重新分配一塊空間,并且加入到系統(tǒng)堆的鏈表中來(lái)。
2.5 空閑鏈表
圖表 6 空閑鏈表
圖表6表示空閑鏈表,鏈表的頭是一個(gè)全局的指針,指向空閑鏈表的第一個(gè)存儲(chǔ)段。空閑鏈表的每個(gè)存儲(chǔ)段都有一個(gè)大小字段和一個(gè)指向下一個(gè)空閑存儲(chǔ)段的指針,鏈表的最后一個(gè)空閑存儲(chǔ)段的Next為空指針。
posted on 2008-04-22 11:20
YODA 閱讀(3632)
評(píng)論(0) 編輯 收藏