《JVM G1源碼分析和調(diào)優(yōu)》 —2.5 內(nèi)存分配和管理
2.5 內(nèi)存分配和管理
C/C++程序員和Java程序員最大的區(qū)別之一就是對(duì)內(nèi)存管理的工作,Java程序員不需要管理內(nèi)存,因?yàn)橛蠮VM幫助管理。所以JVM的所謂開(kāi)發(fā)必然涉及內(nèi)存的分配和管理。我們這里盡可能地簡(jiǎn)化描述內(nèi)存分配和管理,只描述和GC算法相關(guān)的部分。本質(zhì)上來(lái)說(shuō),了解這一部分內(nèi)容越多,特別是了解JVM如何與操作系統(tǒng)交互的部分,越容易對(duì)JVM調(diào)優(yōu)。
JVM作為內(nèi)存分配的管理器,一定涉及如何與內(nèi)存交互。那么JVM是如何管理內(nèi)存的?實(shí)際上內(nèi)存管理的算法很多,簡(jiǎn)單來(lái)說(shuō)JVM從操作系統(tǒng)申請(qǐng)一塊內(nèi)存,然后根據(jù)不同的GC算法進(jìn)行管理。下面以Linux為例看一下JVM是如何做的。
首先JVM先通過(guò)操作系統(tǒng)的系統(tǒng)調(diào)用(system call)進(jìn)行內(nèi)存的申請(qǐng),典型的就是mmap。在這里提一個(gè)問(wèn)題,眾所周知glibc提供了我們常用的內(nèi)存管理函數(shù)如malloc/free/realloc/memcopy/memset等。為什么JVM不直接使用這些函數(shù)?glibc里面的malloc也是通過(guò)mmap等系統(tǒng)調(diào)用來(lái)完成內(nèi)存的分配,之后glibc再對(duì)已經(jīng)分配到的內(nèi)存進(jìn)行管理。GC算法實(shí)現(xiàn)了一套自己的管理方式,所以再基于malloc/free實(shí)現(xiàn)效率肯定不高。mmap必須以PAGE_SIZE為單位進(jìn)行映射,而內(nèi)存也只能以頁(yè)為單位進(jìn)行映射,若要映射非PAGE_SIZE整數(shù)倍的地址范圍,要先進(jìn)行內(nèi)存對(duì)齊,強(qiáng)行以PAGE_SIZE的倍數(shù)大小進(jìn)行映射。還要注意一點(diǎn),操作系統(tǒng)對(duì)內(nèi)存的分配管理典型地分為兩個(gè)階段:保留(reserve)和提交(commit)。保留階段告知系統(tǒng)從某一地址開(kāi)始到后面的dwSize大小的連續(xù)虛擬內(nèi)存需要供程序使用,進(jìn)程其他分配內(nèi)存的操作不得使用這段內(nèi)存;提交階段將虛擬地址映射到對(duì)應(yīng)的真實(shí)物理內(nèi)存中,這樣這塊內(nèi)存就可以正常使用。
對(duì)于保留和提交,Windows在使用VirtualAlloc分配內(nèi)存時(shí)傳遞不同的參數(shù)MEM_RESERVE/MEM_COMMIT,Linux在mmap保留內(nèi)存時(shí)使用MAP_PRIVATE | MAP_NORESERVE | MAP_ANONYMOUS,提交內(nèi)存時(shí)使用MAP_PRIVATE | MAP_FIXED | MAP_ANONYMOUS。其中MAP_NORESERVE指不要為這個(gè)映射保留交換空間,MAP_FIXED使用指定的映射起始地址。
在JVM中我們還看到了使用類(lèi)庫(kù)函數(shù)malloc/free的地方。這和JVM內(nèi)存管理策略有關(guān),JVM內(nèi)部也有很多數(shù)據(jù)需要在堆中分配,而這和Java堆空間沒(méi)有關(guān)系,所以直接使用類(lèi)庫(kù)函數(shù)。另外需要提一下JVM推薦使用jemalloc替代glibc,原因是其效率更高。
JVM中常見(jiàn)的對(duì)象類(lèi)型有以下6種:
ResourceObj:線程有一個(gè)資源空間(Resource Area),一般ResourceObj都位于這里。定義資源空間的目的是對(duì)JVM其他功能的支持,如CFG、在C1/C2優(yōu)化時(shí)可能需要訪問(wèn)運(yùn)行時(shí)信息(這些信息可以保存在線程的資源區(qū))。
StackObj:棧對(duì)象,聲明的對(duì)象使用棧管理。其實(shí)棧對(duì)象并不提供任何功能,且禁止New/Delete操作。對(duì)象分配在線程棧中,或者使用自定義的棧容器進(jìn)行管理。
ValueObj:值對(duì)象,該對(duì)象在堆對(duì)象需要進(jìn)行嵌套時(shí)使用,簡(jiǎn)單地說(shuō)就是對(duì)象分配的位置和宿主對(duì)象(即擁有這個(gè)ValueObj對(duì)象的對(duì)象)是一樣的。
AllStatic:靜態(tài)對(duì)象,全局對(duì)象,只有一個(gè)。值得一提的是C++中靜態(tài)對(duì)象的初始化并沒(méi)有通過(guò)規(guī)范保證,可能會(huì)有一個(gè)問(wèn)題,就是兩個(gè)靜態(tài)對(duì)象相互依賴(lài),那么在初始化的時(shí)候可能出錯(cuò)。JVM中的很多靜態(tài)對(duì)象的初始化,都是顯式調(diào)用靜態(tài)初始化函數(shù)。
MetaspaceObj:元對(duì)象,比如InstanceKlass這樣的元數(shù)據(jù)就是元對(duì)象。
CHeapObj:這是堆空間的對(duì)象,由new/delete/free/malloc管理。其包含的內(nèi)容很多,比如Java對(duì)象、InstanceOop(后面提到的G1對(duì)象分配出來(lái)的對(duì)象)。除了Java對(duì)象,還有其他的對(duì)象也在堆中。
JVM中為了準(zhǔn)確描述這些堆中的對(duì)象,以方便對(duì)JVM進(jìn)行優(yōu)化,所以又定義了更具體的子類(lèi)型,代碼如下所示:
hotspot/src/share/vm/memory/allocation.hpp
// JVM中使用的內(nèi)存類(lèi)型
mtJavaHeap????????? = 0x00,? // Java堆
mtClass???????????? = 0x01,? // JVM中Java類(lèi)
mtThread??????????? = 0x02,? // JVM中線程對(duì)象
mtThreadStack?????? = 0x03,
mtCode????????????? = 0x04,? // JVM中生成的編譯代碼
mtGC??????????????? = 0x05,? // GC的內(nèi)存
mtCompiler????????? = 0x06,? // 編譯器使用的內(nèi)存
mtInternal????????? = 0x07,? // JVM中內(nèi)部使用的類(lèi)型,不屬于上述任何類(lèi)型
mtOther???????????? = 0x08,? // 不是由JVM使用的內(nèi)存
mtSymbol??????????? = 0x09,? // 符號(hào)表使用的內(nèi)存
mtNMT?????????????? = 0x0A,? // NMT使用的內(nèi)存
mtClassShared?????? = 0x0B,? // 共享類(lèi)數(shù)據(jù)
mtChunk???????????? = 0x0C,? // Chunk用于緩存
mtTest????????????? = 0x0D,
mtTracing?????????? = 0x0E,
mtNone????????????? = 0x0F,
這些信息描述了JVM使用內(nèi)存的情況,這一部分信息能夠幫助定位JVM本身運(yùn)行時(shí)出現(xiàn)的問(wèn)題,我們將在最后的附錄B中通過(guò)本地內(nèi)存跟蹤(Native Memory Tracking)來(lái)進(jìn)一步解讀這些信息。
JVM
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶(hù)投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶(hù)投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。