在Java中,內(nèi)存管理是由Java虛擬機(jī)和一個(gè)不需要明確干預(yù)的。作為一種塊結(jié)構(gòu)語言,Java使用一種模型,它的內(nèi)存分為兩種主要類型:堆棧和堆。
局部變量和方法參數(shù)使用基于“堆棧”的內(nèi)存當(dāng)進(jìn)入或退出代碼塊或方法時(shí),這部分內(nèi)存會(huì)自動(dòng)增長(zhǎng)和收縮。在向系統(tǒng)請(qǐng)求一定數(shù)量的內(nèi)存的情況下,內(nèi)存的大小只有在運(yùn)行時(shí)或創(chuàng)建對(duì)象時(shí)才知道,這些請(qǐng)求通常由進(jìn)程內(nèi)存中稱為“動(dòng)態(tài)內(nèi)存”或“堆”的區(qū)域來滿足。嚴(yán)格地說——有這樣一種情況,一個(gè)可能被指定為堆的對(duì)象被寫入堆棧。
這兩個(gè)存儲(chǔ)區(qū)描述如下:
圖一:JVM堆棧和堆內(nèi)存區(qū)域
*方法參數(shù)和局部變量所在的位置
**對(duì)象所在的位置
注意:一個(gè)程序中的所有線程都有自己的堆棧,但是共享一個(gè)堆。線程也可以有自己的小堆緩沖區(qū),稱為線程本地分配緩沖區(qū)(TLAB)。
這種動(dòng)態(tài)堆內(nèi)存的問題是,當(dāng)程序使用完內(nèi)存時(shí),必須釋放內(nèi)存。如果沒有這一點(diǎn),進(jìn)程的大小將會(huì)增長(zhǎng),直到達(dá)到?jīng)]有更多可用內(nèi)存資源的程度。為了幫助解決這個(gè)問題,當(dāng)堆內(nèi)存耗盡,程序不再需要某個(gè)對(duì)象時(shí),Java中的對(duì)象會(huì)被一組線程回收內(nèi)存,這些線程執(zhí)行一項(xiàng)任務(wù),稱為垃圾收集。
簡(jiǎn)而言之,程序員不需要擔(dān)心在Java中釋放內(nèi)存。
雖然這個(gè)過程在Java中是自動(dòng)的,但這并不能保證最佳的系統(tǒng)性能。通過理解Java中內(nèi)存管理過程的工作方式,您可以更加同情JVM,并調(diào)整您的對(duì)象創(chuàng)建方法,以便減少JVM和垃圾收集器的負(fù)載,從而獲得輕微的性能提升。
除了垃圾收集,理解Java內(nèi)存管理的一部分是掌握“對(duì)象分配”的過程因此,本文旨在探索這是什么,并為對(duì)象分配提供一個(gè)類比。通過理解它是什么以及它在內(nèi)存管理中的作用,您可以更好地分析對(duì)象分配對(duì)系統(tǒng)性能的影響。
對(duì)象分配和垃圾收集
在Java中,引用用于訪問對(duì)象,對(duì)象是保存內(nèi)存區(qū)域“地址”的變量,對(duì)象的屬性將存儲(chǔ)在內(nèi)存區(qū)域中。這個(gè)內(nèi)存被分配給堆區(qū)域。當(dāng)您聲明字段或局部變量時(shí),這只是對(duì)堆上對(duì)象的引用,而不是對(duì)象本身。當(dāng)創(chuàng)建一個(gè)對(duì)象時(shí),從堆中請(qǐng)求所需的內(nèi)存量,然后可以通過引用變量訪問這個(gè)對(duì)象。
注意,只要一個(gè)對(duì)象是“可到達(dá)的”,它就是“活的”,垃圾收集器不能破壞它。當(dāng)一個(gè)對(duì)象不再“可達(dá)”時(shí),這個(gè)內(nèi)存就有資格被垃圾收集器回收用于堆。這里值得澄清的是“可達(dá)性”的概念。一個(gè)對(duì)象的引用必須可以從一個(gè)叫做“根集”的地方到達(dá),如圖2所示。根集包括當(dāng)前所有線程堆棧上的所有局部變量和方法參數(shù)。如果兩個(gè)對(duì)象相互引用,但是根集不能到達(dá),它們將被垃圾收集。
圖二:對(duì)象可達(dá)性
對(duì)象創(chuàng)建和銷毀之間的時(shí)間由術(shù)語“對(duì)象生命周期”概括,對(duì)于“短命”對(duì)象,這些對(duì)象保留在堆內(nèi)存的一部分,稱為“Nursery”;這也被稱為“年輕空間”或“伊甸園”。對(duì)于壽命較長(zhǎng)的對(duì)象,通常會(huì)將其移動(dòng)到堆中稱為“Tenured”(“舊空間”)的部分,以便騰出Nursery來分配新對(duì)象。值得一提的是,在大多數(shù)程序中,創(chuàng)建的大多數(shù)對(duì)象都是短暫的;換句話說,它們被創(chuàng)造和釋放得相對(duì)較快,所以它們永遠(yuǎn)不會(huì)到達(dá)終身空間。在任何情況下,垃圾回收通常發(fā)生在需要釋放更多內(nèi)存時(shí)。托兒所是分配更多對(duì)象的地方。這些通常是短暫的,而且由于它通常是一個(gè)較小的區(qū)域,清理Nursery比清理終身空間要快得多。
很重要的一點(diǎn)是,要么將生命周期短于兩次新收集之間的時(shí)間的短生命周期對(duì)象作為目標(biāo),要么將它們作為終身空間中的程序生命周期的目標(biāo),因?yàn)檫@樣可以減少整個(gè)應(yīng)用程序的開銷。在這種情況下,對(duì)象分配非常低,堆大小適合系統(tǒng),垃圾收集很少發(fā)生,不會(huì)影響應(yīng)用程序的運(yùn)行,這是我們?cè)?/span>Chronicle中的目標(biāo)。
圖3:JVM堆中的Nursery空間和Tenured空間
簡(jiǎn)而言之,當(dāng)在類中聲明字段和局部變量時(shí),只為引用分配足夠的內(nèi)存。無論對(duì)象本身的大小如何,引用通常是每個(gè)對(duì)象4或8個(gè)字節(jié),對(duì)象本身被分配在堆上的其他地方。對(duì)象分配是分配對(duì)象內(nèi)存的過程,對(duì)象內(nèi)存是在使用“new”運(yùn)算符時(shí)分配的。
結(jié)論
本文介紹了Java中的內(nèi)存管理,以及對(duì)象分配的簡(jiǎn)單類比,并說明了考慮它的重要性。讓JVM有效地管理動(dòng)態(tài)內(nèi)存是確保最佳系統(tǒng)性能的基礎(chǔ)。