https://en.wikipedia.org/wiki/Garbage_collection_(computer_science)
垃圾收集器
垃圾回收器是算法的實現(xiàn),通常是新生代和老年代使用不同的垃圾回收器組合來處理垃圾回收孽鸡。
-
對象死亡算法
- 引用計數(shù)算法
給對象添加一個引用計數(shù)器,每當有一個地方引用它時計數(shù)器值加1设预;當引用失效時构蹬,計數(shù)器值減1惋鹅;任何時刻計數(shù)器值為0時對象就不可能再被使用则酝。
Person a = new Person(); Person b = new Person(); a.instance = b; b.instance = a; a = null; b = null; System.gc(); //對象被回收,如果引用計數(shù)就會永遠不為0闰集,導致無法回收堤魁。
-
可達性分析算法
通過一系列的"GC Roots"的對象作為起始點,從這些節(jié)點開始向下搜索返十,當一個對象與"GC Roots"沒有任何引用鏈相連妥泉,證明此對象是不可用的。(也稱GC Roots到這個對象不可達)洞坑。
引用鏈:搜索所走過的路徑
GC Roots:虛擬機棧中的引用對象(棧幀中的本地變量表)盲链,
???? ???? ???????? 方法區(qū)中類靜態(tài)屬性引用的對象和常量引用的對象,
???? ???? ???? ????本地方法棧中JNI引用的對象(native方法)迟杂。
- 引用計數(shù)算法
-
垃圾回收算法
標記清除算法
首先標記出所有需要回收的對象刽沾,在標記完成后統(tǒng)一回收所有被標記的對象,它的標記過程就是對象的可達性分析排拷。
不足點:標記和清除過程效率不高
清除后產生大量不連續(xù)的內存碎片侧漓,可能導致分配大對象時無法找到足夠的連續(xù)內存而觸發(fā)另一次回收。復制算法
內存按容量劃分大小相等的兩塊监氢,每次使用其中一塊布蔗,回收時將存活的對象復制到另一塊藤违,然后將已使用的空間一次清理。
不要考慮復雜情況纵揍,只要移動指針順序分配內存顿乒,實現(xiàn)簡單,運行高效泽谨。
代價昂貴璧榄,一半的內存空間。
分成三塊吧雹,一塊較大的Eden空間和兩塊較小的Survivor空間骨杂,每次使用Eden空間和一塊Survivor空間,回收時將存活的對象復制到另一塊Survivor空間雄卷,最后清理掉Eden和Survivor空間搓蚪。默認8:1 Eden和Survivor,所有之后有10%的內存浪費龙亲,當Survivor空間不夠用時陕凹,需要依賴老年代進行分配擔保(Handle Promotion)標記---整理算法
標記過程和標記清除算法一樣悍抑,然后讓所有存活的對象都向一端移動鳄炉,讓后直接清理掉端邊界以外的內存-
分代收集算法
把java堆分為新生代和老年代,根據(jù)各年代特點采用最適當?shù)乃惴ā?br> 新生代搜骡,每次垃圾收集時都發(fā)現(xiàn)有大批對象死去拂盯,只有少量存活,那就選用復制算法记靡。
老年代谈竿,因為對象存活率高、沒有額外空間對它進行分配擔保摸吠,就必須使用“標記-清理或者標記-整理算法進行回收空凸。
大對象很容易提前觸發(fā)垃圾回收,虛擬機提供-XX;PretenureSizeThreshold寸痢,大于這個值呀洲,對象直接在老年代分配,避免新生代發(fā)生大量的內存復制啼止。
虛擬機給每個對象定義了一個對象年齡計數(shù)器道逗,如果對象出生在Eden并經過一次Minor GC后仍存活并且能夠被Survivor容納,就移動到Survivor空間中献烦,并且對象年齡設置為1滓窍,在Survivor中每熬過一次Minor GC年齡就加1,年齡增加到閾值就會晉升到老年代巩那,通過參數(shù)-XX:MaxTenuringThreshold設置吏夯,默認15此蜈。
如果在Survivor空間中相同年齡的所有對象大小的總和大于Survivor空間的一半,年齡大于或者等于該年齡的對象就可以直接進入老年代锦亦,無需等待年齡閾值舶替。
回收方法區(qū)
常量回收:沒有任何地方引用這個字面量陈醒,清理出常量池。
類回收:該類的所有實例都已經回收
該類的classLoader已經被回收
該類對應的java.lang.Class對象沒有任何地方引用
滿足以上3個條件才能回收
參數(shù):-Xnoclassgc瞧甩、-verose:class钉跷、-XX:+TraceClassLoading、-XX:+TraceClassUnLoading
空間分配擔保
在發(fā)生Minor GC之前肚逸,虛擬機會先檢查老年代最大可用的連續(xù)空間是否大于新生代所有對象總空間爷辙,條件成立,那么MinorGC是安全的朦促。如果不成立膝晾,虛擬機會查看HANDLE
pROMOTINfAILURE設置值是否允許擔保失敗,如果允許那么繼續(xù)檢查老年代最大可用的連續(xù)空間是否大于歷次晉升到老年代對象的平均大小务冕,如果大于進行一次MinorGC血当,盡管有風險,如果小于或者不允許禀忆,改為進行一次Full GC臊旭。如果風險失敗即Survivor無法容納,就需要老年代擔保箩退,無法容納的直接進入老年代离熏,如果老年代無法容納,就進行一次FullGC
新生代GC(Minor GC):指發(fā)生在新生代的垃圾回收動作戴涝∽檀粒回收速度比較快
老年代GC(Major GC/ Full GC):指發(fā)生在老年代的垃圾回收動作,看垃圾回收器實現(xiàn)喊括,有時會伴隨Minor GC胧瓜。Major GC的速度一般會比Minor GC慢10倍以上。引用
強引用:就是指在程序代碼之中普遍存在的郑什,類似”O(jiān)bject obj = new Object()“府喳,只要強引用存在,垃圾回收器永遠不會回收蘑拯。
軟引用:是用來描述一些還有用但并非必須的對象钝满。對軟引用關聯(lián)著的對象兜粘,在系統(tǒng)將要發(fā)生內存溢出異常之前,將會把這些對象列進回收范圍之中進行第二次回收弯蚜,如果這次回收還沒有足夠的內存孔轴,才會拋出內存溢出異常。SoftReference類碎捺。
弱引用:用來描述非必需對象的路鹰,被弱引用關聯(lián)的對象只能生存到下一次垃圾收集發(fā)生之前。
虛引用:也稱幽靈引用或者幻影引用收厨,一個對象是否有虛引用存在對其生存時間沒有影響晋柱,也無法通過虛引用獲得對象實例,唯一目的在垃圾回收時收到一個系統(tǒng)通知诵叁,PhantomReference類
對象
-
對象創(chuàng)建
虛擬機遇到一個new指令雁竞,首先將去檢查這個指令的參數(shù)能否在常量池中定位到一個類的符號引用,并檢查這個符號引用代表的類是否已經被加載拧额、解析和初始化過碑诉。如果沒有,那必須先執(zhí)行相應的類加載過程侥锦,類加載檢查通過后进栽,虛擬機為新生對象分配內存。對象所需內存大小在類加載完成時便可完全確定捎拯,把一塊確定大小的內存從java堆上劃分出來泪幌。虛擬機需要將分配到內存空間初始化為零值盲厌,接下來對對象進行必要的設置署照,主要是對象頭設置,現(xiàn)在所有的字段都還為零吗浩,然后執(zhí)行init方法建芙,把對象按照程序員的意愿初始化。
內存分配方式:
指針碰撞:假設內存絕對規(guī)整懂扼,所有用過的內存都放到一邊禁荸,空閑的內存放在另一邊,中間放著一個指針作為分界點的指示器阀湿,分配內存就是把指針往空閑空間那邊挪動一段與對象大小相等的距離赶熟。
空閑列表:維護一個列表,記錄哪些內存塊是可用的陷嘴,在分配的時候從列表中找到一塊足夠大的空間劃分給對象實例映砖,并更新列表上的記錄。
并發(fā)處理:
對分配內存空間的動作進行同步處理---實際上虛擬機采用CAS配上失敗重試的方式保證更新操作的原子性灾挨;
內存分配的動作按照線程劃分在不同的空間之中進行邑退,即每個線程在java堆中預先分配一小塊內存竹宋,稱為本地線程分配緩沖(Thread Local Allocation Buffer,TLAB)地技。哪個線程要分配內存蜈七,就在哪個線程的TLAB上分配,只有TLAB用完并分配新的TLAB時莫矗,才需要同步鎖定飒硅。內存空間初始化為0可以提前到TLAB分配。 -
對象的內存布局
對象頭作谚、實例數(shù)據(jù)狡相、對齊填充
對象頭第一部分(Mark Word),用于存儲對象自身的運行時數(shù)據(jù)食磕,如哈希碼(HashCode)尽棕、GC分代年齡、鎖狀態(tài)標志彬伦、線程持有的鎖滔悉、偏向線程ID、GC標志(空)单绑;
第二部分時類型指針回官,即對象指向它的類元數(shù)據(jù)的指針和數(shù)組長度。
實例數(shù)據(jù):是對象真正存儲的有效信息搂橙,也是在程序代碼中所定義的各種類型的字段內容歉提。
存儲順序受虛擬機分配策略(FieldsAllocationStyle)和源碼定義順序(CompactFields)的影響
虛擬機分配策略,longs/doubles区转、ints苔巨、shorts/chars、bytes/booleans废离、oops(Ordinary Object Pointers)侄泽,相同寬度的字段總是被分配到一起
可以讓子類中較窄的變量插入到父類變量的空隙中
對齊填充不是必然存在,HotSpot VM要求對象起始地址必須是8字節(jié)的整數(shù)倍蜻韭,對象頭正好8個字節(jié)悼尾,對象實例數(shù)據(jù)部分如果沒有對齊時,就要通過對齊填充來補全肖方。 -
對象訪問定位
java程序通過棧上的reference數(shù)據(jù)來操作堆上具體對象
reference類型在虛擬機規(guī)范中只規(guī)定了一個指向對象的引用闺魏,所以對象的訪問方式取決于虛擬機實現(xiàn)。
使用句柄訪問俯画,java堆中劃分出一塊內存作為句柄池析桥,reference中存儲的就是對象的句柄地址,句柄池中存儲堆上對象實例數(shù)據(jù)地址和方法區(qū)對象類型數(shù)據(jù)地址。
垃圾回收時烹骨,不需要修改reference翻伺。
使用指針直接訪問,reference中存儲的是堆上對象地址沮焕,對象分對象頭和實例數(shù)據(jù)吨岭,對象頭指針存方法區(qū)對象類型數(shù)據(jù)地址。
垃圾回收時峦树,要修改reference辣辫;對象訪問快,節(jié)省一次指針定位魁巩。 -
對象死亡
對象在進行可達性分析后發(fā)現(xiàn)沒有與GC Roots相連的引用鏈急灭,將會被第一次標記且進行一次篩選,篩選條件是此對象是否有必要執(zhí)行finalize()方法谷遂,當對象沒有覆蓋finalize()方法或者finalize()方法已經被虛擬機調用過葬馋,視為不需要執(zhí)行,即會被回收肾扰。需要執(zhí)行finalize()方法的對象放在F-Queue的隊列之中畴嘶,并由虛擬機自動創(chuàng)建低優(yōu)先級的Finalizer線程去執(zhí)行它;只要在finalize()方法中重新建立與引用鏈的關聯(lián)即可擺脫被回收集晚,如果對F-Queue進行第二次小規(guī)模標記時沒有建立關聯(lián)窗悯,就會被移出并回收。public class Person() [ public static Person SAVE_HOOK = null; protected void finalize() throws Throwable { super.finalize(); Person.SAVE_HOOK = this; } }
-
垃圾回收器
一般都使用分代算法集成不同的垃圾回收器偷拔。
初始標記:標記一下GC Roots能直接關聯(lián)到的對象
并發(fā)標記:進行GC Roots Tracing 的過程
重新標記:是為了修正并發(fā)標記期間因用戶程序繼續(xù)運行而導致標記產品變動的那一部分對象的標記記錄
并發(fā)清除:清除不能到達GC Roots的對象
重置線程:更新之前使用過的數(shù)據(jù)