垃圾收集機(jī)制的基本原理及方法
2.1 JVM中內(nèi)存的劃分
垃圾收集器對Java程序員來說,基本上是透明的,但是只有了解GC的工作原理、如何優(yōu)化GC的性能、如何與GC進(jìn)行有限的交互,才能提高整個應(yīng)用程序的性能、全面提升內(nèi)存的管理效率,為了說明其工作方式,我們首先看看內(nèi)存中幾種常用的存放數(shù)據(jù)的地方:
(1) 堆棧(Stack):位于常規(guī)RAM(隨機(jī)訪問存儲器)區(qū)域,但可通過它的“堆棧指針”獲得處理器的直接支持。堆棧指針若向下移,會創(chuàng)建新的內(nèi)存;若向上移,則會釋放那些內(nèi)存。這是一種特別快、特別有效的數(shù)據(jù)保存方式,僅次于CPU的寄存器。創(chuàng)建程序時,Java編譯器必須準(zhǔn)確地知道堆棧內(nèi)保存的所有數(shù)據(jù)的“長度”以及“存在時間”。這是由于它必須生成相應(yīng)的代碼,以便向上和向下移動指針。這一限制無疑影響了程序的靈活性,所以盡管有些Java數(shù)據(jù)要保存在堆棧里--特別是對象的引用(也可稱為對象的引用變量),但Java中的對象不會放在其中。
(2) 堆(Heap)。一種常規(guī)用途的內(nèi)存池(也在RAM區(qū)域),其中保存了Java對象。和堆棧不同,“內(nèi)存堆”或“堆”(Heap)最吸引人的地方在于編譯器不必知道要從堆里分配多少存儲空間,也不必知道存儲的數(shù)據(jù)要在堆里停留多長的時間。因此,用堆保存數(shù)據(jù)時會得到更大的靈活性。要求創(chuàng)建一個對象時,只需用new命令編制相關(guān)的代碼即可。執(zhí)行這些代碼時,會在堆里自動進(jìn)行數(shù)據(jù)的保存。當(dāng)然,為達(dá)到這種靈活性,必然會付出一定的代價:在堆里分配存儲空間時會花掉更長的時間!這是導(dǎo)致Java性能不佳的因素之一。
SUN的JVM使用分代方式(Generation)管理堆空間,“代”分配給新舊對象的內(nèi)存池。這些對象的不斷積累會導(dǎo)致一個的內(nèi)存狀態(tài),從而推動垃圾收集的開始。如圖說明了SUN的JVM中堆空間粗略的劃分。
年輕的一代包括年輕的對象空間(eden)和兩個存活(survivor)空間(SS#1和SS#2)。新對象被分配到eden中,那些存活較久的對象則會從年輕的一代轉(zhuǎn)移到老一代中。圖中的Perm段叫做永久代(permanent generation),它保存了JVM的類和方法對象。
(3) 靜態(tài)存儲(Static)。這兒的“靜態(tài)”(Static)是指“位于固定位置”(盡管也在RAM里)或是有且僅有一份。程序運行期間,靜態(tài)存儲的數(shù)據(jù)將隨時等候調(diào)用。可用static關(guān)鍵字指出一個對象的特定元素是靜態(tài)的。但Java對象本身永遠(yuǎn)都不會置入靜態(tài)存儲空間。
2.2 對象在內(nèi)存中的分配?
了解了內(nèi)存中這些存放數(shù)據(jù)的方式后,我們先來看看C++中存放對象的機(jī)制:在C++中,對象可以是在堆棧中創(chuàng)建的,這樣可達(dá)到更快的速度。然而,在C++里創(chuàng)建“內(nèi)存堆”(Heap)對象通常會慢得多。這種內(nèi)存堆實際是一個大的內(nèi)存池,要求必須進(jìn)行再循環(huán)(再生)。這里可以把C++的Heap想象是一塊場地,在這里面中每個對象不斷監(jiān)視屬于自己的地盤,他們可能在以后的某個時刻不再繼續(xù)占用目前所占用的空間,即釋放后的內(nèi)存會在堆里留下一個洞,所以再調(diào)用new的時候,存儲分配機(jī)制必須進(jìn)行某種形式的搜索,使新對象能夠利用已經(jīng)存在的空洞,否則就會很快用光堆的存儲空間。之所以內(nèi)存堆的分配會在C++里對性能造成如此重大的性能影響,對可用內(nèi)存的搜索正是一個重要的原因。所以創(chuàng)建基于堆棧的對象要快得多。
而在Java中的內(nèi)存堆(Heap)更像一條傳送帶:每次分配了一個新對象后,“Heap指針”都會朝前移動,這意味著對象存儲空間的分配可以達(dá)到非常快的速度。因為“Heap指針”只是單純的往前移動至未經(jīng)分配的區(qū)域,所以它與C++的堆棧分配方式幾乎是不相上下的(當(dāng)然,在數(shù)據(jù)記錄上會多花一些開銷,但要比搜索存儲空間快多了)。但是如果只是按那種方式分配,最終就要求進(jìn)行大量的內(nèi)存頁面交換(這對性能的發(fā)揮會產(chǎn)生巨大干擾),而且終究會用光內(nèi)存,出現(xiàn)內(nèi)存分頁錯誤(page fault)。所以Java引入了“垃圾收集器”。它在收集“垃圾”的同時,也負(fù)責(zé)重新緊密排列(compact)堆里的所有對象,消除內(nèi)存空洞,將“堆指針”移至盡可能靠近傳送帶開頭的地方,遠(yuǎn)離發(fā)生(內(nèi)存)分頁錯誤的地點。垃圾收集器會重新安排所有東西,使其成為一個高速、無限自由的堆模型,同時游刃有余地分配存儲空間。但是垃圾收集時的代價是非常高昂的,這也是導(dǎo)致Java性能不佳的因素之一。