本文主要思考三個問題。
哪些內(nèi)存需要回收妥畏?
什么時候回收邦邦?
如何回收?
對象已死么醉蚁?
1.引用計數(shù)算法
給對象中添加一個引用計數(shù)器燃辖,每當有一個地方引用它,計數(shù)器值就加1网棍;當引用時效的時候黔龟,減一。任何時候計數(shù)器為0的對象就是不可能再被引用的滥玷。(很難解決對象之間相互循環(huán)引用的問題)
2.可達性分析算法
這個算法的基本思路就是通過一系列名為"GC Roots"的對象作為起始點氏身,從這些節(jié)點開始向下搜索,搜索所走過的路徑稱為引用鏈(Reference Chain)惑畴,當一個對象到GC Roots沒有任何引用鏈相連時蛋欣,則證明此對象是不可用的,下圖對象object5, object6, object7雖然有互相判斷如贷,但它們到GC Roots是不可達的陷虎,所以它們將會判定為是可回收對象。
java GC ROOTS的對象包含以下幾種
虛擬機棧中引用的對象
方法區(qū)中類靜態(tài)屬性引用的對象
方法區(qū)中常量引用的對象
本地方法棧種JNI(native方法)引用的對象
引用
1.強引用 一直活著
2.軟引用 還有用但并非必須的對象 活到第二次回收
3.弱引用 非必須對象 活到下一次垃圾收集之前
4.虛引用
生存還是死亡
GC算法
1.標記清除算法
分為標記和清除兩個階段:首先標記出所有需要回收的對象杠袱,在標記完成后統(tǒng)一回收所有被標記的對象泻红。該算法的缺點是效率不高并且會產(chǎn)生不連續(xù)的內(nèi)存碎片。
2.復(fù)制算法
把內(nèi)存空間劃為兩個區(qū)域霞掺,每次只使用其中一個區(qū)域。垃圾回收時讹躯,遍歷當前使用區(qū)域菩彬,把正在使用中的對象復(fù)制到另外一個區(qū)域中缠劝。次算法每次只處理正在使用中的對象,因此復(fù)制成本比較小骗灶,同時復(fù)制過去以后還能進行相應(yīng)的內(nèi)存整理惨恭,不會出現(xiàn)“碎片”問題。優(yōu)點:實現(xiàn)簡單耙旦,運行高效脱羡。缺點:會浪費一定的內(nèi)存。一般新生代采用這種算法
3.標記整理算法
標記階段與標記清除算法一樣免都。但后續(xù)并不是直接對可回收的對象進行清理锉罐,而是讓所有存活對象都想一端移動,然后清理绕娘。優(yōu)點是不會造成內(nèi)存碎片
4.分代收集算法
只是根據(jù)對象存貨周期的不用將內(nèi)存劃分為幾塊脓规。一般是把java堆劃分為新生代和老年代。這樣就可以根據(jù)各個年代的特點采用最適當?shù)氖謾C算法险领。在新生代中侨舆,每次垃圾收集時有大量的對象死亡,只有少量的存在绢陌,那就采用復(fù)制算法挨下,對于老年代,對象存活率高脐湾,沒有額外空間對他進行分配擔(dān)保臭笆,就必須使用“標記-清理”或者“標記-整理”算法來進行回收。
Java中垃圾回收器的類型
Java提供多種類型的垃圾回收器沥割。JVM中的垃圾收集一般都采用“分代收集”耗啦,不同的堆內(nèi)存區(qū)域采用不同的收集算法,主要目的就是為了增加吞吐量或降低停頓時間机杜。
- Serial收集器:新生代收集器帜讲,使用復(fù)制算法,使用一個線程進行GC椒拗,串行似将,其它工作線程暫停。
- ParNew收集器:新生代收集器蚀苛,使用復(fù)制算法在验,Serial收集器的多線程版,用多個線程進行GC堵未,并行腋舌,其它工作線程暫停。使用-XX:+UseParNewGC開關(guān)來控制使用ParNew+Serial Old收集器組合收集內(nèi)存渗蟹;使用-XX:ParallelGCThreads來設(shè)置執(zhí)行內(nèi)存回收的線程數(shù)块饺。
- Parallel Scavenge 收集器:吞吐量優(yōu)先的垃圾回收器赞辩,作用在新生代,使用復(fù)制算法授艰,關(guān)注CPU吞吐量辨嗽,即運行用戶代碼的時間/總時間。使用-XX:+UseParallelGC開關(guān)控制使用Parallel Scavenge+Serial Old收集器組合回收垃圾淮腾。
- Serial Old收集器:老年代收集器糟需,單線程收集器,串行谷朝,使用標記整理算法洲押,使用單線程進行GC,其它工作線程暫停徘禁。
- Parallel Old收集器:吞吐量優(yōu)先的垃圾回收器诅诱,作用在老年代,多線程送朱,并行娘荡,多線程機制與Parallel Scavenge差不錯,使用標記整理算法驶沼,在Parallel Old執(zhí)行時炮沐,仍然需要暫停其它線程。
- CMS(Concurrent Mark Sweep)收集器:老年代收集器回怜,致力于獲取最短回收停頓時間(即縮短垃圾回收的時間)大年,使用標記清除算法,多線程玉雾,優(yōu)點是并發(fā)收集(用戶線程可以和GC線程同時工作)翔试,停頓小。使用-XX:+UseConcMarkSweepGC進行ParNew+CMS+Serial Old進行內(nèi)存回收复旬,優(yōu)先使用ParNew+CMS(原因見Full GC和并發(fā)垃圾回收一節(jié))垦缅,當用戶線程內(nèi)存不足時,采用備用方案Serial Old收集驹碍。
- GI收集器
與GC有關(guān)的JVM參數(shù)
做GC調(diào)優(yōu)需要大量的實踐壁涎,耐心和對項目的分析。我曾經(jīng)參與過高容量志秃,低延遲的電商系統(tǒng)怔球,在開發(fā)中我們需要通過分析造成Full GC的原因來提高系統(tǒng)性能,在這個過程中我發(fā)現(xiàn)做GC的調(diào)優(yōu)很大程度上依賴于對系統(tǒng)的分析浮还,系統(tǒng)擁有怎樣的對象以及他們的平均生命周期竟坛。
舉個例子,如果一個應(yīng)用大多是短生命周期的對象,那么應(yīng)該確保Eden區(qū)足夠大流码,這樣可以減少Minor GC的次數(shù)又官。可以通過-XX:NewRatio來控制新生代和老年代的比例漫试,比如-XX:NewRatio=3代表新生代和老年代的比例為1:3。需要注意的是碘赖,擴大新生代的大小會減少老年代的大小驾荣,這會導(dǎo)致Major GC執(zhí)行的更頻繁,而Major GC可能會造成用戶線程的停頓從而降低系統(tǒng)吞吐量普泡。JVM中可以用NewSize和MaxNewSize參數(shù)來指定新生代內(nèi)存最小和最大值播掷,如果兩個參數(shù)值一樣,那么就相當于固定了新生代的大小撼班。
個人建議歧匈,在做GC調(diào)優(yōu)之前最好深入理解Java中GC機制,推薦閱讀Sun Microsystems提供的有關(guān)GC的文檔砰嘁。這個鏈接可能會對理解GC機制提供一些幫助件炉。下面的圖列出了各個區(qū)可用的一些JVM參數(shù)。
總結(jié)
- 為了分代垃圾回收矮湘,Java堆內(nèi)存分為3代:新生代斟冕,老年代和永久代。
- 新的對象實例會優(yōu)先分配在新生代缅阳,在經(jīng)歷幾次Minor GC后(默認15次)磕蛇,還存活的會被移至老年代(某些大對象會直接在老年代分配)。
- 永久代是否執(zhí)行GC十办,取決于采用的JVM秀撇。
- Minor GC發(fā)生在新生代,當Eden區(qū)沒有足夠空間時向族,會發(fā)起一次Minor GC呵燕,將Eden區(qū)中的存活對象移至Survivor區(qū)。Major GC發(fā)生在老年代炸枣,當升到老年代的對象大于老年代剩余空間時會發(fā)生Major GC虏等。
- 發(fā)生Major GC時用戶線程會暫停,會降低系統(tǒng)性能和吞吐量适肠。
- JVM的參數(shù)-Xmx和-Xms用來設(shè)置Java堆內(nèi)存的初始大小和最大值霍衫。依據(jù)個人經(jīng)驗這個值的比例最好是1:1或者1:1.5。比如侯养,你可以將-Xmx和-Xms都設(shè)為1GB敦跌,或者-Xmx和-Xms設(shè)為1.2GB和1.8GB。
- Java中不能手動觸發(fā)GC,但可以用不同的引用類來輔助垃圾回收器工作(比如:弱引用或軟引用)柠傍。