? ? 垃圾回收一般是在Java堆,方法區(qū)中進行在张,因為堆中幾乎存放了Java中所有的對象實例杭抠。我們討論的垃圾回收機制一般僅僅作用于jvm堆區(qū)和方法區(qū)泼各,因為它們是thread共享的夷磕,這部分內(nèi)存的分配和回收都是動態(tài)的履肃。
? ? 1??.如何確定某個對象是“垃圾”?
? ? 在所有語言中坐桩,幾乎都要涉及對象是否存活的問題尺棋,比如C++/OC中給對象添加了一個引用計數(shù)機制,當需要的時候绵跷,這個引用的計數(shù)器就會+1膘螟;當引用失效的時候它的計數(shù)器-1;當計數(shù)器=0時碾局,說明該對象不可能再被使用荆残。但是引用計數(shù)機制有個問題,就是很難發(fā)現(xiàn)净当,解決循環(huán)計數(shù)問題:
? ? 程序創(chuàng)建了一個指針内斯,指向了一個計數(shù)為1的A對象,對象A創(chuàng)建了對象B(計數(shù)器也為1)作為組合的子對象像啼。對象A在創(chuàng)建完了俘闯,也會創(chuàng)建對象B,因為依賴忽冻,所以對象A擁有對象B的強引用(指針)≌胬剩現(xiàn)在如果子對象B也有一個指向?qū)ο驛的強引用(指針),那么A的計數(shù)器會變?yōu)?甚颂,如下圖pic2.
? ? 當有一天pointer不需要A對象了(調(diào)用A的析構(gòu)函數(shù))蜜猾,A的counter值變?yōu)?,不過由于對象B的計數(shù)值仍然為1振诬,兩個對象的計數(shù)值!=0,所以都沒有釋放掉蹭睡,造成內(nèi)存泄露。
? ? C++和OC也都有各種方法解決這個問題赶么,自動化的智能指針/arc技術(shù)等肩豁,但是還是會遇到更棘手的循環(huán)計數(shù)問題。所以Java的用了更加適合辫呻,方便的清理技術(shù)清钥。
? ? Java中采取了 可達性分析法。該方法的基本思想是通過一系列的“GC Roots”對象作為起點進行搜索放闺,如果在“GC Roots”和一個對象之間沒有可達路徑祟昭,則稱該對象是不可達的,不過要注意的是被判定為不可達的對象不一定就會成為可回收對象怖侦。被判定為不可達的對象要成為可回收對象必須至少經(jīng)歷兩次標記過程篡悟,如果在這兩次標記過程中仍然沒有逃脫成為可回收對象的可能性谜叹,則基本上就真的成為可回收對象了“嵩幔可達性分析法具體是如何操作的荷腊,我覺得是圖論中的連通性分析算法。(P.S. GC Roots = jvm棧的引用對象 or 方法區(qū)static對象 or 方法區(qū)常量 or JNI的引用對象)急凰。
? ?2??.典型的垃圾收集算法
? ? 1.Mark-Sweep(標記-清除)算法
? ? 標記-清除算法是最基礎(chǔ)的收集算法女仰,它分為“標記”和“清除”兩個階段:首先標記出所需回收的對象,在標記完成后統(tǒng)一回收掉所有被標記的對象抡锈,它的標記過程其實就是前面的根搜索算法中判定垃圾對象的標記過程疾忍。
這個算法有如下缺點:
? ? -標記和清除過程的效率都不高企孩。
? ? -標記清除后會產(chǎn)生大量不連續(xù)的內(nèi)存碎片锭碳,空間碎片太多可能會導(dǎo)致,當程序在以后的運行過程中需要分配較大對象時無法找到足夠的連續(xù)內(nèi)存而不得不觸發(fā)另一次垃圾收集動作勿璃。
? ? 2.Copying(復(fù)制)算法
? ?復(fù)制算法是針對標記—清除算法的基礎(chǔ)上進行改進而得到的擒抛,它將jvm內(nèi)存按容量分為大小相等的兩塊(但是實際應(yīng)用中不是),每次只使用其中半塊补疑,當這一塊的內(nèi)存用完了歧沪,就將還存活著的對象復(fù)制到另外一塊內(nèi)存上面,然后再把已使用過的內(nèi)存空間一次清理掉莲组。
復(fù)制算法優(yōu)點是 一.簡單诊胞,只需移動棧頂指針,按順序分配內(nèi)存即可锹杈。 二. 高效, 每次只對一塊內(nèi)存進行回收撵孤。三. 不會有內(nèi)存碎片問題。
缺點很明顯竭望,可一次性分配的最大內(nèi)存縮小了一半邪码。
? ? 3.Mark-Compact(標記-整理)算法
? ? 為了解決Copying算法的缺點,充分利用內(nèi)存空間咬清,有了Mark-Compact算法闭专。該算法標記階段和Mark-Sweep一樣,但是在完成標記之后旧烧,它不是直接清理可回收對象影钉,而是將存活對象都向一端移動,然后去除掉端邊界以外的內(nèi)存掘剪。具體過程如下圖所示:
? ? 4.Generational Collection(分代收集)算法
? ? 分代收集算法是目前Hotspot垃圾收集器采用的算法平委。它將內(nèi)存劃分為若干個不同的區(qū)域:分為老年代(Tenured Generation)和新生代(Young Generation),在新生代中夺谁,每次垃圾收集時都會發(fā)現(xiàn)有大量對象死去(90%以上)廉赔,只有少量存活愚墓,因此可選用復(fù)制算法來完成收集,不會有太大的復(fù)制開銷昂勉,而老年代中因為對象存活率高、沒有額外空間對它進行分配擔(dān)保扫腺,就必須使用標記—清除算法或標記—整理算法來進行回收岗照。
新生代:新創(chuàng)建的對象都存放在這里。因為大多數(shù)對象很快變得不可達笆环,所以大多數(shù)對象在年輕代中創(chuàng)建攒至,然后消失。這里的垃圾清除叫“minor GC”躁劣。
老年代:沒有變得不可達迫吐,存活下來的年輕代對象被復(fù)制到這里。因為它更大的規(guī)模账忘,GC發(fā)生的次數(shù)比在年輕代的少志膀。這里的垃圾清除叫“major GC”(或“full GC”)發(fā)生了。其速度一般會比Minor GC慢10倍以上鳖擒。另外溉浙,如果分配了Direct Memory,在老年代中進行Full GC時蒋荚,會順便清理掉Direct Memory中的廢棄對象戳稽。
永久代(permanent generation)也稱為“方法區(qū)(method area)”,他存儲class對象和字符串常量期升,靜態(tài)變量惊奇。不是永久的存放從老年代存活下來的對象的。在這塊內(nèi)存中有可能發(fā)生垃圾回收播赁。發(fā)生在這里垃圾回收也被稱為Full GC颂郎。
? ?3??. 年輕代組成部分
? ? -對象優(yōu)先在Eden分配。
? ? -大對象直接進入老年代行拢。
? ? -長期存活的對象將進入老年代祖秒。
? ? 年輕代總共有3塊空間,其中2塊為Survivor區(qū)舟奠。
? 執(zhí)行順序如下:
絕大多數(shù)新創(chuàng)建的對象分配在Eden區(qū)竭缝。
在Eden區(qū)發(fā)生一次GC后,存活的對象移到其中一個Survivor區(qū)沼瘫。
在Eden區(qū)發(fā)生一次GC后抬纸,對象是存放到Survivor區(qū),這個Survivor區(qū)已經(jīng)存在其他存活的對象耿戚。
一旦一個Survivor區(qū)已滿湿故,存活的對象移動到另外一個Survivor區(qū)阿趁。然后之前那個空間已滿Survivor區(qū)將置為空,沒有任何數(shù)據(jù)坛猪。
經(jīng)過重復(fù)多次這樣的步驟后依舊存活的對象將被移到老年代脖阵。當對象在Survivor區(qū)躲過一次GC的話,其對象年齡便會加1墅茉,默認情況下命黔,如果對象年齡達到15歲,就會移動到老年代中就斤。
?4??.典型的垃圾收集器
1.Serial/Serial Old ?2.ParNew?3.Parallel Scavenge?4.Parallel Old?5.CMS
6. G1:G1是jdk1.7的新的收集器悍募,替換1.5的CMS,其具有并發(fā)洋机,分代收集坠宴,空間整合,可預(yù)測停頓時間模型等特點的新一代收集器技術(shù)绷旗。
代碼來講解:
public class Main
{
? ? public static final int_1MB=1024*1024;
? ? public static voidmain(String[] args)
? ? {
? ? ? ? byte[] a1,a2,a3,a4;
? ? ? ? a1 =new byte[2*_1MB];
? ? ? ? a2 =new byte[2*_1MB];
? ? ? ? a3 =new byte[3*_1MB];
? ? ? ? a4 =new byte[4*_1MB];
? ? }
}
結(jié)果:[GC (Allocation Failure) [PSYoungGen: 5439K->464K(9216K)] 5439K->4568K(19456K), 0.0037039 secs] [Times: user=0.00 sys=0.01, real=0.00 secs]
Heap
PSYoungGen? ? ? total 9216K, used 7922K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
eden space 8192K, 91% used [0x00000007bf600000,0x00000007bfd48af8,0x00000007bfe00000)
from space 1024K, 45% used [0x00000007bfe00000,0x00000007bfe74010,0x00000007bff00000)
to? space 1024K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007c0000000)
ParOldGen? ? ? total 10240K, used 4104K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
object space 10240K, 40% used [0x00000007bec00000,0x00000007bf002020,0x00000007bf600000)
Metaspace? ? ? used 2918K, capacity 4494K, committed 4864K, reserved 1056768K
class space? ? used 324K, capacity 386K, committed 512K, reserved 1048576K
(VM參數(shù)是:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8)
? ? 從結(jié)果來看喜鼓,分配a4的時候發(fā)生了一次minor gc,這次gc結(jié)果是年輕代從5439K變?yōu)?64K衔肢。a1颠通,a2,a3占用了7MB膀懈,新生代一共10MB顿锰,所以a4要分配進來,必須發(fā)生minor gc启搂,但是此時又發(fā)現(xiàn)survivor區(qū)不能放進a1或者a2或者a3(1MB大小)硼控,所以只好通過分配擔(dān)保機制前轉(zhuǎn)移到老生代去。gc結(jié)束后胳赌,4MB的a4放入老生代牢撼。
? ? 5??.空間分配擔(dān)保
? ? 在發(fā)生gc之前,vm先檢查老年代最大的可用連續(xù)空間是否大于年輕代所有對象總空間疑苫。如果條件成立熏版,minor gc確保成功,如果不成立vm看HandlePromotionFailure設(shè)置的值是否允許擔(dān)保失敗捍掺,如果允許撼短,那么會檢查老年代最大可用的連續(xù)空間是否大于歷次老年代對象的平均值,大于就進行minor gc挺勿,小于就該為full gc曲横。