淺談關(guān)于Java的GC垃圾回收器的一些基本概念
一、基本回收算法
1. 引用計(jì)數(shù)(Reference Counting)
比較古老的回收算法。原理是此對(duì)象有一個(gè)引用,即增加一個(gè)計(jì)數(shù),刪除一個(gè)引用則減少一個(gè)計(jì)數(shù)。垃圾回收時(shí),只用收集計(jì)數(shù)為0的對(duì)象。此算法最致命的是無(wú)法處理循環(huán)引用的問題。
2. 標(biāo)記-清除(Mark-Sweep)
此算法執(zhí)行分兩階段。第一階段從引用根節(jié)點(diǎn)開始標(biāo)記所有被引用的對(duì)象,第二階段遍歷整個(gè)堆,把未標(biāo)記的對(duì)象清除。此算法需要暫停整個(gè)應(yīng)用,同時(shí),會(huì)產(chǎn)生內(nèi)存碎片。
3. 復(fù)制(Copying)
此算法把內(nèi)存空間劃為兩個(gè)相等的區(qū)域,每次只使用其中一個(gè)區(qū)域。垃圾回收時(shí),遍歷當(dāng)前使用區(qū)域,把正在使用中的對(duì)象復(fù)制到另外一個(gè)區(qū)域中。次算法每次只處理正在使用中的對(duì)象,因此復(fù)制成本比較小,同時(shí)復(fù)制過(guò)去以后還能進(jìn)行相應(yīng)的內(nèi)存整理,不過(guò)出現(xiàn)“碎片”問題。當(dāng)然,此算法的缺點(diǎn)也是很明顯的,就是需要兩倍內(nèi)存空間。
4. 標(biāo)記-整理(Mark-Compact)
此算法結(jié)合了 “標(biāo)記-清除”和“復(fù)制”兩個(gè)算法的優(yōu)點(diǎn)。也是分兩階段,第一階段從根節(jié)點(diǎn)開始標(biāo)記所有被引用對(duì)象,第二階段遍歷整個(gè)堆,把清除未標(biāo)記對(duì)象并且把存活對(duì)象 “壓縮”到堆的其中一塊,按順序排放。此算法避免了“標(biāo)記-清除”的碎片問題,同時(shí)也避免了“復(fù)制”算法的空間問題。
5. 增量收集(Incremental Collecting)
實(shí)施垃圾回收算法,即:在應(yīng)用進(jìn)行的同時(shí)進(jìn)行垃圾回收。不知道什么原因JDK5.0中的收集器沒有使用這種算法的。
6. 分代(Generational Collecting)
基于對(duì)對(duì)象生命周期分析后得出的垃圾回收算法。把對(duì)象分為年青代、年老代、持久代,對(duì)不同生命周期的對(duì)象使用不同的算法(上述方式中的一個(gè))進(jìn)行回收?,F(xiàn)在的垃圾回收器(從J2SE1.2開始)都是使用此算法的。
1. Young(年輕代)
年輕代分三個(gè)區(qū)。一個(gè)Eden區(qū),兩個(gè) Survivor區(qū)。大部分對(duì)象在Eden區(qū)中生成。當(dāng)Eden區(qū)滿時(shí),還存活的對(duì)象將被復(fù)制到Survivor區(qū)(兩個(gè)中的一個(gè)),當(dāng)這個(gè) Survivor區(qū)滿時(shí),此區(qū)的存活對(duì)象將被復(fù)制到另外一個(gè)Survivor區(qū),當(dāng)這個(gè)Survivor去也滿了的時(shí)候,從第一個(gè)Survivor區(qū)復(fù)制過(guò)來(lái)的并且此時(shí)還存活的對(duì)象,將被復(fù)制“年老區(qū)(Tenured)”。需要注意,Survivor的兩個(gè)區(qū)是對(duì)稱的,沒先后關(guān)系,所以同一個(gè)區(qū)中可能同時(shí)存在從Eden復(fù)制過(guò)來(lái)對(duì)象,和從前一個(gè)Survivor復(fù)制過(guò)來(lái)的對(duì)象,而復(fù)制到年老區(qū)的只有從第一個(gè)Survivor去過(guò)來(lái)的對(duì)象。而且,Survivor區(qū)總有一個(gè)是空的。
2. Tenured(年老代)
年老代存放從年輕代存活的對(duì)象。一般來(lái)說(shuō)年老代存放的都是生命期較長(zhǎng)的對(duì)象。
3. Perm(持久代)
用于存放靜態(tài)文件,如今Java類、方法等。持久代對(duì)垃圾回收沒有顯著影響,但是有些應(yīng)用可能動(dòng)態(tài)生成或者調(diào)用一些class,例如Hibernate等,在這種時(shí)候需要設(shè)置一個(gè)比較大的持久代空間來(lái)存放這些運(yùn)行過(guò)程中新增的類。持久代大小通過(guò)-XX:MaxPermSize=<N>進(jìn)行設(shè)置。
二、GC類型
GC有兩種類型:Scavenge GC和Full GC。
1. Scavenge GC
一般情況下,當(dāng)新對(duì)象生成,并且在Eden申請(qǐng)空間失敗時(shí),就好觸發(fā)Scavenge GC,堆Eden區(qū)域進(jìn)行GC,清除非存活對(duì)象,并且把尚且存活的對(duì)象移動(dòng)到Survivor區(qū)。然后整理Survivor的兩個(gè)區(qū)。
2. Full GC
對(duì)整個(gè)堆進(jìn)行整理,包括Young、Tenured和Perm。Full GC比Scavenge GC要慢,因此應(yīng)該盡可能減少Full GC。有如下原因可能導(dǎo)致Full GC:
* Tenured被寫滿
* Perm域被寫滿
* System.gc()被顯示調(diào)用
* 上一次GC之后Heap的各域分配策略動(dòng)態(tài)變化
分代垃圾回收過(guò)程演示
1.
<!--StartFragment -->
2.
3.
4.
二、垃圾回收器
目前的收集器主要有三種:串行收集器、并行收集器、并發(fā)收集器。
1. 串行收集器
使用單線程處理所有垃圾回收工作,因?yàn)闊o(wú)需多線程交互,所以效率比較高。但是,也無(wú)法使用多處理器的優(yōu)勢(shì),所以此收集器適合單處理器機(jī)器。當(dāng)然,此收集器也可以用在小數(shù)據(jù)量(100M左右)情況下的多處理器機(jī)器上??梢允褂?XX:+UseSerialGC打開。
2. 并行收集器
1. 對(duì)年輕代進(jìn)行并行垃圾回收,因此可以減少垃圾回收時(shí)間。一般在多線程多處理器機(jī)器上使用。使用-XX:+UseParallelGC.打開。并行收集器在J2SE5.0第六6更新上引入,在Java SE6.0中進(jìn)行了增強(qiáng)--可以堆年老代進(jìn)行并行收集。如果年老代不使用并發(fā)收集的話,是使用單線程進(jìn)行垃圾回收,因此會(huì)制約擴(kuò)展能力。使用-XX:+UseParallelOldGC打開。
2. 使用-XX:ParallelGCThreads=<N>設(shè)置并行垃圾回收的線程數(shù)。此值可以設(shè)置與機(jī)器處理器數(shù)量相等。
3. 此收集器可以進(jìn)行如下配置:
* 最大垃圾回收暫停:指定垃圾回收時(shí)的最長(zhǎng)暫停時(shí)間,通過(guò)-XX:MaxGCPauseMillis=<N>指定。<N>為毫秒.如果指定了此值的話,堆大小和垃圾回收相關(guān)參數(shù)會(huì)進(jìn)行調(diào)整以達(dá)到指定值。設(shè)定此值可能會(huì)減少應(yīng)用的吞吐量。
* 吞吐量:吞吐量為垃圾回收時(shí)間與非垃圾回收時(shí)間的比值,通過(guò)-XX:GCTimeRatio=<N>來(lái)設(shè)定,公式為1/(1+N)。例如,-XX:GCTimeRatio=19時(shí),表示5%的時(shí)間用于垃圾回收。默認(rèn)情況為99,即1%的時(shí)間用于垃圾回收。
3. 并發(fā)收集器
可以保證大部分工作都并發(fā)進(jìn)行(應(yīng)用不停止),垃圾回收只暫停很少的時(shí)間,此收集器適合對(duì)響應(yīng)時(shí)間要求比較高的中、大規(guī)模應(yīng)用。使用-XX:+UseConcMarkSweepGC打開。
1. 并發(fā)收集器主要減少年老代的暫停時(shí)間,他在應(yīng)用不停止的情況下使用獨(dú)立的垃圾回收線程,跟蹤可達(dá)對(duì)象。在每個(gè)年老代垃圾回收周期中,在收集初期并發(fā)收集器會(huì)對(duì)整個(gè)應(yīng)用進(jìn)行簡(jiǎn)短的暫停,在收集中還會(huì)再暫停一次。第二次暫停會(huì)比第一次稍長(zhǎng),在此過(guò)程中多個(gè)線程同時(shí)進(jìn)行垃圾回收工作。
2. 并發(fā)收集器使用處理器換來(lái)短暫的停頓時(shí)間。在一個(gè)N個(gè)處理器的系統(tǒng)上,并發(fā)收集部分使用K/N個(gè)可用處理器進(jìn)行回收,一般情況下1<=K<=N/4。
3. 在只有一個(gè)處理器的主機(jī)上使用并發(fā)收集器,設(shè)置為incremental mode模式也可獲得較短的停頓時(shí)間。
4. 浮動(dòng)垃圾:由于在應(yīng)用運(yùn)行的同時(shí)進(jìn)行垃圾回收,所以有些垃圾可能在垃圾回收進(jìn)行完成時(shí)產(chǎn)生,這樣就造成了“Floating Garbage”,這些垃圾需要在下次垃圾回收周期時(shí)才能回收掉。所以,并發(fā)收集器一般需要20%的預(yù)留空間用于這些浮動(dòng)垃圾。
5. Concurrent Mode Failure:并發(fā)收集器在應(yīng)用運(yùn)行時(shí)進(jìn)行收集,所以需要保證堆在垃圾回收的這段時(shí)間有足夠的空間供程序使用,否則,垃圾回收還未完成,堆空間先滿了。這種情況下將會(huì)發(fā)生“并發(fā)模式失敗”,此時(shí)整個(gè)應(yīng)用將會(huì)暫停,進(jìn)行垃圾回收。
6. 啟動(dòng)并發(fā)收集器:因?yàn)椴l(fā)收集在應(yīng)用運(yùn)行時(shí)進(jìn)行收集,所以必須保證收集完成之前有足夠的內(nèi)存空間供程序使用,否則會(huì)出現(xiàn)“Concurrent Mode Failure”。通過(guò)設(shè)置-XX:CMSInitiatingOccupancyFraction=<N>指定還有多少剩余堆時(shí)開始執(zhí)行并發(fā)收集
4. 小結(jié)
* 串行處理器:
--適用情況:數(shù)據(jù)量比較小(100M左右);單處理器下并且對(duì)響應(yīng)時(shí)間無(wú)要求的應(yīng)用。
--缺點(diǎn):只能用于小型應(yīng)用
* 并行處理器:
--適用情況:“對(duì)吞吐量有高要求”,多CPU、對(duì)應(yīng)用響應(yīng)時(shí)間無(wú)要求的中、大型應(yīng)用。舉例:后臺(tái)處理、科學(xué)計(jì)算。
--缺點(diǎn):應(yīng)用響應(yīng)時(shí)間可能較長(zhǎng)
* 并發(fā)處理器:
--適用情況:“對(duì)響應(yīng)時(shí)間有高要求”,多CPU、對(duì)應(yīng)用響應(yīng)時(shí)間有較高要求的中、大型應(yīng)用。舉例:Web服務(wù)器/應(yīng)用服務(wù)器、電信交換、集成開發(fā)環(huán)境。
三、GC基本原理
GC(Garbage Collection),是JAVA/.NET中的垃圾收集器。
Java是由C++發(fā)展來(lái)的,它擯棄了C++中一些繁瑣容易出錯(cuò)的東西,引入了計(jì)數(shù)器的概念,其中有一條就是這個(gè)GC機(jī)制(C#借鑒了JAVA)
編程人員容易出現(xiàn)問題的地方,忘記或者錯(cuò)誤的內(nèi)存回收會(huì)導(dǎo)致程序或系統(tǒng)的不穩(wěn)定甚至崩潰,Java提供的GC功能可以自動(dòng)監(jiān)測(cè)對(duì)象是否超過(guò)作用域從而達(dá)到自動(dòng)回收內(nèi)存的目的,Java語(yǔ)言沒有提供釋放已分配內(nèi)存的顯示操作方法。所以,Java的內(nèi)存管理實(shí)際上就是對(duì)象的管理,其中包括對(duì)象的分配和釋放。
對(duì)于程序員來(lái)說(shuō),分配對(duì)象使用new關(guān)鍵字;釋放對(duì)象時(shí),只要將對(duì)象所有引用賦值為null,讓程序不能夠再訪問到這個(gè)對(duì)象,我們稱該對(duì)象為"不可達(dá)的".GC將負(fù)責(zé)回收所有"不可達(dá)"對(duì)象的內(nèi)存空間。
對(duì)于GC來(lái)說(shuō),當(dāng)程序員創(chuàng)建對(duì)象時(shí),GC就開始監(jiān)控這個(gè)對(duì)象的地址、大小以及使用情況。通常,GC采用有向圖的方式記錄和管理堆(heap)中的所有對(duì)象。通過(guò)這種方式確定哪些對(duì)象是"可達(dá)的",哪些對(duì)象是"不可達(dá)的".當(dāng)GC確定一些對(duì)象為"不可達(dá)"時(shí),GC就有責(zé)任回收這些內(nèi)存空間。但是,為了保證 GC能夠在不同平臺(tái)實(shí)現(xiàn)的問題,Java規(guī)范對(duì)GC的很多行為都沒有進(jìn)行嚴(yán)格的規(guī)定。例如,對(duì)于采用什么類型的回收算法、什么時(shí)候進(jìn)行回收等重要問題都沒有明確的規(guī)定。因此,不同的JVM的實(shí)現(xiàn)者往往有不同的實(shí)現(xiàn)算法。這也給Java程序員的開發(fā)帶來(lái)行多不確定性。本文研究了幾個(gè)與GC工作相關(guān)的問題,努力減少這種不確定性給Java程序帶來(lái)的負(fù)面影響。
四、GC分代劃分
JVM內(nèi)存模型中Heap區(qū)分兩大塊,一塊是 Young Generation,另一塊是Old Generation
1) 在Young Generation中,有一個(gè)叫Eden Space的空間,主要是用來(lái)存放新生的對(duì)象,還有兩個(gè)Survivor Spaces(from、to),它們的大小總是一樣,它們用來(lái)存放每次垃圾回收后存活下來(lái)的對(duì)象。
2) 在Old Generation中,主要存放應(yīng)用程序中生命周期長(zhǎng)的內(nèi)存對(duì)象。
3) 在Young Generation塊中,垃圾回收一般用Copying的算法,速度快。每次GC的時(shí)候,存活下來(lái)的對(duì)象首先由Eden拷貝到某個(gè)SurvivorSpace,當(dāng)Survivor Space空間滿了后,剩下的live對(duì)象就被直接拷貝到OldGeneration中去。因此,每次GC后,Eden內(nèi)存塊會(huì)被清空。
4) 在Old Generation塊中,垃圾回收一般用mark-compact的算法,速度慢些,但減少內(nèi)存要求。
5) 垃圾回收分多級(jí),0級(jí)為全部(Full)的垃圾回收,會(huì)回收OLD段中的垃圾;1級(jí)或以上為部分垃圾回收,只會(huì)回收Young中的垃圾,內(nèi)存溢出通常發(fā)生于OLD段或Perm段垃圾回收后,仍然無(wú)內(nèi)存空間容納新的Java對(duì)象的情況。
五、增量式GC
增量式GC(Incremental GC),是GC在JVM中通常是由一個(gè)或一組進(jìn)程來(lái)實(shí)現(xiàn)的,它本身也和用戶程序一樣占用heap空間,運(yùn)行時(shí)也占用CPU。
當(dāng)GC進(jìn)程運(yùn)行時(shí),應(yīng)用程序停止運(yùn)行。因此,當(dāng)GC運(yùn)行時(shí)間較長(zhǎng)時(shí),用戶能夠感到Java程序的停頓,另外一方面,如果GC運(yùn)行時(shí)間太短,則可能對(duì)象回收率太低,這意味著還有很多應(yīng)該回收的對(duì)象沒有被回收,仍然占用大量?jī)?nèi)存。因此,在設(shè)計(jì)GC的時(shí)候,就必須在停頓時(shí)間和回收率之間進(jìn)行權(quán)衡。一個(gè)好的GC實(shí)現(xiàn)允許用戶定義自己所需要的設(shè)置,例如有些內(nèi)存有限的設(shè)備,對(duì)內(nèi)存的使用量非常敏感,希望GC能夠準(zhǔn)確的回收內(nèi)存,它并不在意程序速度的快慢。另外一些實(shí)時(shí)網(wǎng)絡(luò)游戲,就不能夠允許程序有長(zhǎng)時(shí)間的中斷。
增量式GC就是通過(guò)一定的回收算法,把一個(gè)長(zhǎng)時(shí)間的中斷,劃分為很多個(gè)小的中斷,通過(guò)這種方式減少GC對(duì)用戶程序的影響。雖然,增量式GC在整體性能上可能不如普通GC的效率高,但是它能夠減少程序的最長(zhǎng)停頓時(shí)間。
Sun JDK提供的HotSpot JVM就能支持增量式GC。HotSpot JVM缺省GC方式為不使用增量GC,為了啟動(dòng)增量GC,我們必須在運(yùn)行Java程序時(shí)增加-Xincgc的參數(shù)。
HotSpot JVM增量式GC的實(shí)現(xiàn)是采用Train GC算法,它的基本想法就是:將堆中的所有對(duì)象按照創(chuàng)建和使用情況進(jìn)行分組(分層),將使用頻繁高和具有相關(guān)性的對(duì)象放在一隊(duì)中,隨著程序的運(yùn)行,不斷對(duì)組進(jìn)行調(diào)整。當(dāng)GC運(yùn)行時(shí),它總是先回收最老的(最近很少訪問的)的對(duì)象,如果整組都為可回收對(duì)象,GC將整組回收。這樣,每次GC運(yùn)行只回收一定比例的不可達(dá)對(duì)象,保證程序的順暢運(yùn)行。
- Java full gc觸發(fā)情況實(shí)例解析
- 淺析Java中的GC垃圾回收器的意義及與GC的交互
- Java中垃圾回收器GC對(duì)吞吐量的影響測(cè)試
- 全面解析Java中的GC與幽靈引用
- 在windows下揪出java程序占用cpu很高的線程并完美解決
- 線上Java程序占用CPU過(guò)高解決方案
- Java進(jìn)程cpu頻繁100%問題解決方案
- java應(yīng)用cpu占用過(guò)高問題分析及解決方法
- Java中的線程死鎖是什么?如何避免?
- Java線程死鎖實(shí)例及解決方法
- Java并發(fā)編程預(yù)防死鎖過(guò)程詳解
- Java系統(tǒng)運(yùn)行緩慢等問題的排查思路
相關(guān)文章
MyBatis Oracle 自增序列的實(shí)現(xiàn)方法
這篇文章給大家分享MyBatis Oracle 自增序列的實(shí)現(xiàn)方法及mybatis配置oracle的主鍵自增長(zhǎng)的方法,非常不錯(cuò)具有一定的參考借鑒價(jià)值,感興趣的朋友一起看看吧2016-11-11springmvc—handlermapping三種映射方式
這篇文章主要介紹了springmvc—handlermapping三種映射方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09Spring Data + Thymeleaf 3 + Bootstrap 4 實(shí)現(xiàn)分頁(yè)器實(shí)例代碼
本篇文章主要介紹了Spring Data + Thymeleaf 3 + Bootstrap 4 實(shí)現(xiàn)分頁(yè)器實(shí)例代碼,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2017-05-05使用SpringSecurity 進(jìn)行自定義Token校驗(yàn)
這篇文章主要介紹了使用SpringSecurity 進(jìn)行自定義Token校驗(yàn)操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06Spring Boot緩存實(shí)戰(zhàn) Caffeine示例
本篇文章主要介紹了Spring Boot緩存實(shí)戰(zhàn) Caffeine示例,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-02-02Spring Cloud 服務(wù)網(wǎng)關(guān)Zuul的實(shí)現(xiàn)
這篇文章主要介紹了Spring Cloud 服務(wù)網(wǎng)關(guān)Zuul的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-07-07