Java 垃圾回收機制 [ 內存管理 GC]
一. 什么是Java垃圾回收
當新建一個對象的時候,一個變量指向該對象,同時系統會在堆中分配一塊空間給該對象;當對象不再被任何變量引用的時候,這塊內存就成為了垃 圾,等待GC回收。因為在Java中沒有像C++中那樣的delete語句來完成內存回收。其本意是想代碼編寫者可以更方便的實現自己的功能,不用考慮內 存的問題。但事實并非如此。
二. GC的特征
1. GC 只負責回收堆內存中的對象,而不會回收物理資源;
2. GC 無法被代碼編寫者精確控制,之知道當變量不再不被引用時才會被回收;
3. GC在回收任何對象之前,總會調用它的finalize 方法,該方法可能是該對象重新激活,從而導致回收失敗;
三. GC機制
在開始了解GC機制之前,我們先來看一下在Java中的內存分配機制。
每當申請一個Java 線程時,它擁有自己的內存棧(stack).用來存放局部變量和返回值;在棧里的數據名字是已經舍棄了的,其名字由JVM自己生成的;棧是在線程啟動時分配的。
但所有的線程共享一個內存堆(heap),所有運行時的內存分配都在堆上進行.換句話說就是所有的對象都是在堆上創建的。
其中堆棧的大小可以在Java虛擬機啟動時,在JAVA_OPTS進行配置:
-Xms:初始Heap大小,使用的最小內存,cpu性能高時此值應設的大一些;
-Xmx:java heap最大值,使用的最大內存;
(上面兩個值是分配JVM的最小和最大內存,取決于硬件物理內存的大小,建議均設為物理內存的一半。)
-Xss:每個線程的Stack大小;
那我們看看GC到底是負責那方面的內存回收:
static 變量是在在靜態存儲區域分配的,內存在程序編譯時就分配好了,當程序死亡時,才回去進行釋放。
各種原始數據類型的局部變量,都是在棧上創建的。當程序退出該變量的作用范圍時,這些變量的內存會被自動釋放。
對象都是在堆中創建的,程序運行的時候用new 創建對象,對象創建時會在堆中0為其分配內存。
可以看到在堆中的內存是沒有一個明確的釋放時間的,其空間由自動的存儲管理系統進行控制.也就是Garbage Collector。垃圾收集器通常作為一
個獨立的線程運行.
(一) 分代復制垃圾收集器
JVM使用分代復制(generational copying)算法。分代復制算法基于這樣一個事實——超過95%的對象的生存期都非常 短。分代復制算法根據對象的生存期將對象分為兩代。所有新創建的對象都在一個棧結構的內存區域進行分配,這塊區域叫做eden。這首先使得內存分配的速度 提高了。因為此時只需要更新eden 的指針和檢查eden是否溢出即可。當eden 區域已經全部分配給對象時,大部分對象已經”死亡”。垃圾收集器只 有將少量未死亡的長期對象(tenured 對象)復制到另一塊內存中去,然后直接更新eden 的指針即可。其分代是根據該段內存生命周期的長短來劃分 的。
生存于eden 中的對象稱為新生代(young generation)。
生命較長的對象區域稱為舊生代(old generation)。
其中復制是指,當eden 的內存區域分配完畢,GC 就會對eden 進行一次次要垃圾收集(minor collection),將仍然被引用的對象復制到old generation,并更新指向這些對象的引用,然后將eden 全部清空。
分代復制的效率非常高,因為清空eden花費的時間極少,主要的時間花費在復制上。因此,適當調整eden 的大小,增長或縮短較小的垃圾 收集發生的周期,是的較小的垃圾收集發生時。更多的對象已經死亡,從而減少需要復制的對象的數量,加快程序的速度。當old generation 的內 存區域全部分配完時,垃圾收集器會進行一次主要垃圾收集(major collection)。主要垃圾收集通常比次要垃圾收集慢,因為所有”存活”的對 象都會被遍歷到。可見次要垃圾回收是指將“存活”對象復制到old generation 內存區域中。
其中 young generation = eden + survivor space*2
(二) 標記垃圾收集器
在舊生代中,通常采用標記垃圾收集器,這種垃圾收集器從一組根引用開始,遍歷所有的對象,如果一個對象被根引用,那么就標記為“存活”;而 存活對象引用的對象也被標記為“存活”,如此循環遞歸,其余對象則被標記為“死亡”。死亡的對象將會被回收。根據對存活對象處理方法的不同,標記垃圾收集 器又分為兩類:
1. 標記緊縮垃圾收集器(mark-and-compact collector) 將所有存活的對象復制到一個連續的內存區域中,因此可以有效地減少內存碎片。
2. 標記清除垃圾收集器(mark-and-sweep collector) 保留所有的存活對象,而將所有的死亡對象的內存空 降記錄到一個自由空間列表中,雖然連續的死亡對象空間會被合并,但與標記緊縮垃圾收集器相比,這種方法仍然會產生較多的內存碎片。
(三) 增量垃圾收集器
運行標記緊縮垃圾收集器,或者標記清除垃圾收集器,對運行的程序來說,都是一場災難.因為這些垃圾收集器在運行的時候,會停止JVM 中其 他程序的線程。而且這些垃圾收集器每次都會收集盡可能多的垃圾內存。所以每次停頓的時間是不可預測的。分代復制垃圾收集器的情況稍好一點,但也會造成明顯 的停頓。
增量垃圾收集器能夠提供接近于常數的固定的暫停時間,這個暫停短到用戶可以忽略它,因此增量垃圾收集器特別適用于操縱大量數據的應用程序, 以及對交互性要求比較高的程序。它的基本原理是時間較長的舊生代的垃圾收集分為許多的間隔來完成,每次只收集一部分內存。對于用戶來說好像完全沒有停頓一 樣。但是增量垃圾收集器并不能精確地保證停頓的時間,根據JVM 的實現可以應用與不同要求。
(四) 次要收集和主要收集
次要收集: ?當eden 空間被分配完時,就會發生一次次要垃圾收集(minor collection)。eden中仍然存活的對象會被 復制到survivor space1 中,其他對象直接丟棄,其占用的內存被回收。在一次次要垃圾回收之后,JVM 繼續在eden中創建對象。當 eden的空間再次被分配完的時候,又會發生一次次要垃圾收集。將eden中存活的對象復制到另一個survivor space2 中,并且為 survivor中的每一個對象計算年齡age和存活期threshold。age是對象在復制到old generation之前經歷過的次要垃圾收集 的次數。threshold則是表示在這一次的次要垃圾收集將會被復制到old generation的對象的age。age 小于 threshold 的將會被復制survivor space2 中這些對象被稱為aged 對象(老化對象)。而清空的 survivor space1 為下一次的次要垃圾收集中復制的目的地。
主要收集:在舊生帶中的內存也告罄的時,就會發生一次主要垃圾收集。主要垃圾收集采用標記緊縮的方法,在舊生帶中標記出所有存活的對象,然后將其它對象回 收.主要垃圾收集通常要比次要垃圾收集的時間長的多,因為標記和回收都是很費時的操作。在典型的配置中,舊生帶也比新生帶要大(以達到多進行次要收集少進 行主要收集的目的)。
主要參考: