垃圾回收是什么蓖谢?
Java 虛擬機垃圾回收是指對不使用的內(nèi)存區(qū)域進行釋放阅畴,防止分配空間時因內(nèi)存不足而出現(xiàn)內(nèi)存溢出異常领斥。
哪些內(nèi)存需要回收?
垃圾回收主要發(fā)生在 Java 堆和方法區(qū)中哩都,Java 堆和方法區(qū)是 Java 虛擬機管理內(nèi)存中的兩個區(qū)域,其中 Java 堆主要是用來存放 Java 程序中的對象實例婉徘,方法區(qū)則用來存儲已加載的類信息漠嵌、常量、靜態(tài)變量等數(shù)據(jù)盖呼。Java 虛擬機管理的內(nèi)存中還有其他幾個區(qū)域:程序計數(shù)器儒鹿、Java 虛擬機棧、本地方法棧几晤、運行時常量池约炎、直接內(nèi)存,這幾個區(qū)域?qū)?nèi)存的使用比較具有確定性蟹瘾,所以不需要考慮回收策略,然而 Java 堆和方法區(qū)則不一樣,創(chuàng)建對象實例胎撇、加載類热幔、定義常量等操作都是在程序運行期間進行的,這兩個區(qū)域的內(nèi)存使用是動態(tài)的众雷,因此需要特定的垃圾回收策略進行管理內(nèi)存灸拍。
如何回收做祝?
一般是使用一種叫分代收集算法的綜合策略進行回收,這種算法是把內(nèi)存根據(jù)使用狀態(tài)的不同劃分為幾塊鸡岗。其中 Java 堆大致分為兩塊:新生代和老年代剖淀,新生代存儲新創(chuàng)建或變動頻繁的對象的內(nèi)存信息,老年代則存儲存活時間較長纤房、不經(jīng)常變動的對象的內(nèi)存信息纵隔。方法區(qū)內(nèi)存則一般分為永久代,這里的內(nèi)存變動頻率更加低炮姨,每次垃圾回收只有少數(shù)的廢棄常量和無用的類會被回收捌刮。下面主要說下針對新生代和老年代的內(nèi)存回收策略。
最基礎(chǔ)的策略是標記-清除算法舒岸,首先標記出所有需要回收的對象绅作,之后統(tǒng)一釋放對象占用的內(nèi)存空間。由于回收的對象在內(nèi)存中不一定是連續(xù)存儲的蛾派,所以這種算法執(zhí)行之后可能分產(chǎn)生大量不連續(xù)的內(nèi)存碎片俄认,這可能會導(dǎo)致因無法分配較大對象而再次觸發(fā)垃圾回收動作。
為了提高效率洪乍,針對新生代的垃圾回收一般采用復(fù)制算法眯杏,將內(nèi)存按容量比例分成8:1:1三塊內(nèi)存,其中8份為常用空間壳澳,剩下兩份為倆兩塊保留空間岂贩。使用內(nèi)存時使用常用空間和一塊保留空間,剩下一塊保留空間空閑不使用巷波,回收時萎津,將使用的內(nèi)存中存活的對象一次性復(fù)制到空閑的保留空間里,然后清理使用的常用空間和保留空間抹镊。如果在回收時锉屈,空閑的保留空間不夠用于存儲存活的對象時,那么會使用其他內(nèi)存區(qū)域如老年代進行分配擔保垮耳。
老年代中的對象存活率較高颈渊,采用復(fù)制算法效率反而會比較低,所以針對這塊的垃圾回收一般是直接使用標記-清除算法氨菇,或使用標記-整理算法儡炼,這種算法也是先標記對象,然后將存活的對象向內(nèi)存的一端移動查蓉,最后直接釋放邊界外的內(nèi)存區(qū)域即可乌询。
怎么判斷一個對象需要回收?
上面說到需要對要回收的對象進行先標記之后才能進行回收操作豌研,那么怎么知道那些對象需要回收呢妹田?
判斷對象是否可以繼續(xù)使用主要與對象的引用有關(guān)唬党,下表描述了一個對象的幾種引用狀態(tài)。
引用類型 | 失效時機 | 作用 | 實現(xiàn) |
---|---|---|---|
強引用 | 一直有效 | 防止一個對象被回收 | 直接引用鬼佣,如 Object o = new Object() |
軟引用 | 內(nèi)存溢出之前失效 | 防止一個對象導(dǎo)致的內(nèi)存溢出 | 使用SoftReference類 |
弱引用 | 垃圾收集器工作時失效 | 防止一個對象導(dǎo)致的內(nèi)存泄漏 | 使用WeakReference類 |
虛引用 | 一直無效 | 可以收到一個對象被回收時的通知 | 使用PhantomReference類 |
判斷對象可用狀態(tài)一般的策略是使用引用計數(shù)算法驶拱,就是每個對象有一個引用計數(shù)器,沒當有一個有效的引用時晶衷,計數(shù)器就加1 蓝纲,當引用失效時,計數(shù)器就減1晌纫,計數(shù)器為0的對象就是不能再被使用的税迷,需要被回收。但這種策略會出現(xiàn)對象相互循環(huán)引用而無法回收的問題锹漱,舉例如下:
//a1箭养、a2的引用計數(shù)分別為1
A a1 = new A();
A a2 = new A();
//a2被a1的member成員引用,所以a2的引用計數(shù)為2
a1.member = a2;
//a1同a2哥牍,引用計數(shù)為2
a2.member = a1;
//a1毕泌、a2對象的引用計數(shù)減1,都為1
a1 = null;
a2 = null;
如果這時候采用引用計數(shù)法進行垃圾回收操作嗅辣,那么由于a1和a2對象引用計數(shù)不為0撼泛,將不會被回收。但其實 Java 虛擬機還是可以回收它們的辩诞,這是因為 Java 虛擬機使用了另外一種策略判斷對象是否可被回收坎弯,叫可達性分析算法。
可達性分析算法是先把一些對象看作是 GC Roots 對象译暂,對象間的引用關(guān)系可以看成 GC Roots 作為根節(jié)點而每個引用作為子節(jié)點的樹狀結(jié)構(gòu),如果一個對象在這顆樹之外撩炊,就是從 GC Roots 向下搜索找不到這個對象外永,那么可以說 GC Roots 到這個對象不可達,這個對象就是可以被回收的拧咳。
總結(jié)
本文對 Java 虛擬機中的垃圾回收進行了簡單的介紹伯顶,在 Java 堆中,它需要先根據(jù)對象的引用分析可達性骆膝,以標記出要回收的對象祭衩,然后使用分代收集算法進行回收,而在方法區(qū)中阅签,則是直接回收廢棄的常量和類信息掐暮,最終達到釋放內(nèi)存空間的目的。