Java垃圾回收(GC)机制
一、為什么要垃圾回收
如果不進(jìn)行垃圾回收,內(nèi)存遲早都會被消耗空,因為我們在不斷的分配內(nèi)存空間而不進(jìn)行回收。除非內(nèi)存無限大,我們可以任性的分配而不回收,但是事實并非如此。所以,垃圾回收是必須的。
哪些內(nèi)存需要回收?
哪些內(nèi)存需要回收是垃圾回收機制第一個要考慮的問題,所謂“要回收的垃圾”無非就是那些不可能再被任何途徑使用的對象。那么如何找到這些對象?(如何判斷一個對象是否可以被回收)
二、如何判斷一個對象是否可以被回收
2.1、引用技術(shù)算法
這個算法的實現(xiàn)是,給對象中添加一個引用計數(shù)器,每當(dāng)一個地方引用這個對象時,計數(shù)器值+1;當(dāng)引用失效時,計數(shù)器值-1。任何時刻計數(shù)值為0的對象就是不可能再被使用的。引用計數(shù)為 0 的對象可被回收。正因為循環(huán)引用的存在,因此 Java 虛擬機不使用引用計數(shù)算法。
package com.baidu.GC;public class ReferenceCountingGC {private Object instance = null;private static final int _1MB = 1024 * 1024;/** 這個成員屬性唯一的作用就是占用一點內(nèi)存 */private byte[] bigSize = new byte[2 * _1MB];public static void main(String[] args) {ReferenceCountingGC objectA = new ReferenceCountingGC();ReferenceCountingGC objectB = new ReferenceCountingGC();objectA.instance = objectB;objectB.instance = objectA;objectA = null;objectB = null;System.gc();} }2.2、可達(dá)性分析算法
通過 GC Roots 作為起始點進(jìn)行搜索,從這些節(jié)點向下搜索,搜索所走過的路徑稱為引用鏈,當(dāng)一個對象到GC Roots沒有任何引用鏈(即GC Roots到對象不可達(dá))時,則證明此對象是不可用的。能夠到達(dá)到的對象都是存活的,不可達(dá)的對象可被回收。
那么問題又來了,如何選取GC Roots對象呢?在Java語言中,可以作為GC Roots的對象包括下面幾種:
1、虛擬機棧(棧幀中的局部變量區(qū),也叫做局部變量表)中引用的對象。
2、本地方法棧中JNI(Native方法)引用的對象。
3、方法區(qū)中的類靜態(tài)屬性引用的對象。
4、 方法區(qū)中常量引用的對象。
下面給出一個GCRoots的例子,如下圖,為GCRoots的引用鏈。
由圖可知,obj8、obj9、obj10都沒有到GCRoots對象的引用鏈,即便obj9和obj10之間有引用鏈,他們還是會被當(dāng)成垃圾處理,可以進(jìn)行回收。
2.3、方法區(qū)的回收
因為方法區(qū)主要存放永久代對象,而永久代對象的回收率比新生代低很多,因此在方法區(qū)上進(jìn)行回收性價比不高。
主要是對常量池的回收和對類的卸載。
2.4、finalsize()
finalize() 類似 C++ 的析構(gòu)函數(shù),用來做關(guān)閉外部資源等工作。但是 try-finally 等方式可以做的更好,并且該方法運行代價高昂,不確定性大,無法保證各個對象的調(diào)用順序,因此最好不要使用。
當(dāng)一個對象可被回收時,如果需要執(zhí)行該對象的 finalize() 方法,那么就有可能通過在該方法中讓對象重新被引用,從而實現(xiàn)自救。自救只能進(jìn)行一次,如果回收的對象之前調(diào)用了 finalize() 方法自救,后面回收時不會調(diào)用 finalize() 方法。
三、四種引用狀態(tài)
3.1、強引用
被強引用關(guān)聯(lián)的對象不會被回收。使用 new 一個新對象的方式來創(chuàng)建強引用。
3.2、軟引用
被軟引用關(guān)聯(lián)的對象只有在內(nèi)存不夠的情況下才會被回收。使用 SoftReference 類來創(chuàng)建軟引用。
3.3、弱引用
被弱引用關(guān)聯(lián)的對象一定會被回收,也就是說它只能存活到下一次垃圾回收發(fā)生之前。
使用 WeakReference 類來實現(xiàn)弱引用。
3.4、虛引用
又稱為幽靈引用或者幻影引用。一個對象是否有虛引用的存在,完全不會對其生存時間構(gòu)成影響,也無法通過虛引用取得一個對象。為一個對象設(shè)置虛引用關(guān)的唯一目的就是能在這個對象被回收時收到一個系統(tǒng)通知。
使用 PhantomReference 來實現(xiàn)虛引用。
四、垃圾回收算法
4.1、標(biāo)記-清除(Mark-Sweep)算法
將存活的對象進(jìn)行標(biāo)記,然后清理掉未被標(biāo)記的對象。
不足:
1、標(biāo)記和清除過程效率都不高;
2、會產(chǎn)生大量不連續(xù)的內(nèi)存碎片,導(dǎo)致無法給大對象分配內(nèi)存
4.2、標(biāo)記-整理(Mark-Compact)算法
讓所有存活的對象都向一端移動,然后直接清理掉端邊界以外的內(nèi)存
4.3、復(fù)制(Copying)算法
將內(nèi)存劃分為大小相等的兩塊,每次只使用其中一塊,當(dāng)這一塊內(nèi)存用完了就將還存活的對象復(fù)制到另一塊上面,然后再把使用過的內(nèi)存空間進(jìn)行一次清理。
主要不足是只使用了內(nèi)存的一半。
現(xiàn)在的商業(yè)虛擬機都采用這種收集算法來回收新生代,但是并不是將新生代劃分為大小相等的兩塊,而是分為一塊較大的 Eden 空間和兩塊較小的 Survivor 空間,每次使用 Eden 空間和其中一塊 Survivor。在回收時,將 Eden 和 Survivor 中還存活著的對象一次性復(fù)制到另一塊 Survivor 空間上,最后清理 Eden 和使用過的那一塊 Survivor。
HotSpot 虛擬機的 Eden 和 Survivor 的大小比例默認(rèn)為 8:1,保證了內(nèi)存的利用率達(dá)到 90%。如果每次回收有多于 10% 的對象存活,那么一塊 Survivor 空間就不夠用了,此時需要依賴于老年代進(jìn)行分配擔(dān)保,也就是借用老年代的空間存儲放不下的對象
4.4、分代收集
現(xiàn)在的商業(yè)虛擬機采用分代收集算法,它根據(jù)對象存活周期將內(nèi)存劃分為幾塊,不同塊采用適當(dāng)?shù)氖占惴ā?/p>
一般將堆分為新生代和老年代。
- 新生代使用: 大批對象死去、少量對象存活的(新生代),使用復(fù)制算法,復(fù)制成本低;
- 老年代使用: 對象存活率高、沒有額外空間進(jìn)行分配擔(dān)保的(老年代),采用標(biāo)記-清理算法或者標(biāo)記-整理算法。
五、垃圾收集器
7個垃圾收集器
以上是 HotSpot 虛擬機中的 7 個垃圾收集器,連線表示垃圾收集器可以配合使用。
- 單線程與多線程: 單線程指的是垃圾收集器只使用一個線程進(jìn)行收集,而多線程使用多個線程;
- 串行與并行: 串行指的是垃圾收集器與用戶程序交替執(zhí)行,這意味著在執(zhí)行垃圾收集的時候需要停頓用戶程序;并形指的是垃圾收集器和用戶程序同時執(zhí)行。除了 CMS 和 G1 之外,其它垃圾收集器都是以串行的方式執(zhí)行。
https://www.cnblogs.com/super-jing/p/10795099.html
垃圾回收器G1 詳解
https://www.pdai.tech/md/java/jvm/java-jvm-gc-g1.html
六、內(nèi)存分配與回收機制
6.1、Minor GC 和 Full GC
Minor GC 和 Full GC
Minor GC: 發(fā)生在新生代上,因為新生代對象存活時間很短,因此 Minor GC 會頻繁執(zhí)行,執(zhí)行的速度一般也會比較快。
Full GC: 發(fā)生在老年代上,老年代對象其存活時間長,因此 Full GC 很少執(zhí)行,執(zhí)行速度會比 Minor GC 慢很多。
6.2、內(nèi)存分配策略
2.1、對象優(yōu)先在Eden分配
大多數(shù)情況下,對象在新生代 Eden 區(qū)分配,當(dāng) Eden 區(qū)空間不夠時,發(fā)起 Minor GC。
2.2、大對象直接進(jìn)入老年代
大對象是指需要連續(xù)內(nèi)存空間的對象,最典型的大對象是那種很長的字符串以及數(shù)組。
經(jīng)常出現(xiàn)大對象會提前觸發(fā)垃圾收集以獲取足夠的連續(xù)空間分配給大對象。
-XX:PretenureSizeThreshold,大于此值的對象直接在老年代分配,避免在 Eden 區(qū)和 Survivor 區(qū)之間的大量內(nèi)存復(fù)制。
2.3、長期存活的對象進(jìn)入老年代
為對象定義年齡計數(shù)器,對象在 Eden 出生并經(jīng)過 Minor GC 依然存活,將移動到 Survivor 中,年齡就增加 1 歲,增加到一定年齡則移動到老年代中。
-XX:MaxTenuringThreshold 用來定義年齡的閾值。
6.3、Full GC的觸發(fā)條件
對于 Minor GC,其觸發(fā)條件非常簡單,當(dāng) Eden 空間滿時,就將觸發(fā)一次 Minor GC。而 Full GC 則相對復(fù)雜,有以下條件
3.1、調(diào)用 System.gc()
只是建議虛擬機執(zhí)行 Full GC,但是虛擬機不一定真正去執(zhí)行。不建議使用這種方式,而是讓虛擬機管理內(nèi)存
3.2、 老年代空間不足
老年代空間不足的常見場景為前文所講的大對象直接進(jìn)入老年代、長期存活的對象進(jìn)入老年代等。
為了避免以上原因引起的 Full GC,應(yīng)當(dāng)盡量不要創(chuàng)建過大的對象以及數(shù)組。除此之外,可以通過 -Xmn 虛擬機參數(shù)調(diào)大新生代的大小,讓對象盡量在新生代被回收掉,不進(jìn)入老年代。還可以通過 -XX:MaxTenuringThreshold 調(diào)大對象進(jìn)入老年代的年齡,讓對象在新生代多存活一段時間
3.3、JDK 1.7 及以前的永久代空間不足
在 JDK 1.7 及以前,HotSpot 虛擬機中的方法區(qū)是用永久代實現(xiàn)的,永久代中存放的為一些 Class 的信息、常量、靜態(tài)變量等數(shù)據(jù)。
當(dāng)系統(tǒng)中要加載的類、反射的類和調(diào)用的方法較多時,永久代可能會被占滿,在未配置為采用 CMS GC 的情況下也會執(zhí)行 Full GC。如果經(jīng)過 Full GC 仍然回收不了,那么虛擬機會拋出 java.lang.OutOfMemoryError。
為避免以上原因引起的 Full GC,可采用的方法為增大永久代空間或轉(zhuǎn)為使用 CMS GC
總結(jié)
以上是生活随笔為你收集整理的Java垃圾回收(GC)机制的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 重构(Refactoring)技巧读书笔
- 下一篇: 黑马Java学习笔记之-----集合框架