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