⑴ JVMGC及內存分配策略
GC就是垃圾回收,它的主要作用就是回收程序中不再使用的內存.
JVM執行GC內存回收的時候如何判斷內存是否可以回收呢,就是看對象是否還存活,如果不存活則回收對象對一個的內存空間
那判斷對象是否存活有哪些方法呢?
引用計數法顧名思義就是通過計算對象被引用的次數來判斷對象是否還存活。
例如下面的代碼片段
這種情況下可以理解為o p都是堆內存中的對象,另外p對象的 name 被 o 引用(這里 name欄位也是一個對象)。如下
那麼此時, o的引用計數為 1 , p的引用計數為2。也就是說引用計數法判斷這個引用值為0的時候就會來回收這個對象所佔用的內存。
這種方式的優缺點是什麼呢
優點
缺點
java判斷對象是否存活的依據就是可達性分析。
JVM根據一些GC Roots來進行可達性判斷,若從某一個或某幾個GC Root可以訪問到這個對象那麼這個對象就不可回收。即從這些節點開始向下搜索,搜索所走過的路徑稱為引用鏈(Reference Chain),當一個對象到GC Roots沒有任何引用鏈相連時,則證明此對象是不可用的。
那麼GC Roots有哪些呢
一般我們創建對象的操作都是使用的強引用
一些有用但是並非必需,用軟引用關聯的對象,系統將要發生OOM之前,這些對象就會被回收。
虛擬機參數:
一些有用(程度比軟引用更低)但是並非必需,用弱引用關聯的對象,只能生存到下一次垃圾回收之前,GC發生時,不管內存夠不夠,都會被回收。
幽靈引用,最弱,被垃圾回收的時候收到一個通知
這種處理方式但是容易出現內存的碎片導致以後在程序運行過程中需要分配較大對象時,無法找到足夠的連續內存而不得不提前觸發另一次垃圾收集動作。
將可用內存按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當這一塊的內存用完了,就將還存活著的對象復制到另外一塊上面,然後再把已使用過的內存空間一次清理掉。這樣使得每次都是對整個半區進行內存回收,內存分配時也就不用考慮內存碎片等復雜情況,只要按順序分配內存即可,實現簡單,運行高效。這種演算法的代價是將內存縮小為了原來的一半,造成內存的浪費。
當前商業虛擬機的垃圾收集都採用「分代收集」(Generational Collection)演算法,根據對象存活周期的不同將內存劃分為幾塊。一般是把Java堆分為新生代和老年代,這樣就可以根據各個年代的特點採用最適當的收集演算法。
專門研究表明,新生代中的對象98%是「朝生夕死」的,所以並不需要按照1:1的比例來劃分內存空間,而是將內存分為一塊較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden和其中一塊Survivor[1]。當回收時,將Eden和Survivor中還存活著的對象一次性地復制到另外一塊Survivor空間上,最後清理掉Eden和剛才用過的Survivor空間。HotSpot虛擬機默認Eden和Survivor的大小比例是8:1:1,也就是每次新生代中可用內存空間為整個新生代容量的90%(80%+10%),只有10%的內存會被「浪費」。當然,98%的對象可回收只是一般場景下的數據,我們沒有辦法保證每次回收都只有不多於10%的對象存活,當Survivor空間不夠用時,需要依賴其他內存(這里指老年代)進行分配擔保(Handle Promotion)。
在新生代中,每次垃圾收集時都發現有大批對象死去,只有少量存活,那就選用復制演算法,只需要付出少量存活對象的復製成本就可以完成收集。而老年代中因為對象存活率高、沒有額外空間對它進行分配擔保,就必須使用「標記—清理」或者「標記—整理」演算法來進行回收。
最古老的,單線程,獨占式,成熟,適合單CPU 伺服器
和Serial基本沒區別,唯一的區別:多線程,多CPU的,停頓時間比Serial少
獲取最短回收停頓時間為目標的收集器。
目前很大一部分的Java應用集中在互聯網站或者B/S系統的服務端上,這類應用尤其重視服務的響應速度,希望系統停頓時間最短,以給用戶帶來較好的體驗。CMS收集器就非常符合這類應用的需求。
從名字(包含「Mark Sweep」)上就可以看出,CMS收集器是基於「標記—清除」演算法實現的,它的運作過程相對於前面幾種收集器來說更復雜一些,整個過程分為4個步驟,包括:
浮動垃圾:由於CMS並發清理階段用戶線程還在運行著,伴隨程序運行自然就還會有新的垃圾不斷產生,這一部分垃圾出現在標記過程之後,CMS無法在當次收集中處理掉它們,只好留待下一次GC時再清理掉。這一部分垃圾就稱為「浮動垃圾」。同時用戶的線程還在運行,需要給用戶線程留下運行的內存空間。
特點:
G1當出現內存不足的的情況,也可能進行的FullGC回收。
GC收集器和我們GC調優的目標就是盡可能的減少Stop Thr World 的時間和次數。
ZGC通過技術手段把stw的情況控制在僅有一次,就是第一次的初始標記才會發生,這樣也就不難理解為什麼GC停頓時間不隨著堆增大而上升了,再大我也是通過並發的時間去回收了
關鍵技術(這部分以後在找相關的知識補充)
⑵ 【JVM】對象分配與回收--垃圾回收機制
對象回收需要確認三件事,那些需要回收(對象存活判定,二次標記),何時回收(GC觸發條件)以及如何回收(垃圾回收演算法,垃圾回收器)
1)引用計數法
2)可達性分析:GCRoots作為起始點,沿著引用鏈搜索。
GCRoots可以為:虛擬機棧中引用的對象;本地方法棧中native引用的對象;方法區中類靜態屬性引用的對象;方法區中常量引用的對象;
1)強引用:即常見的那種引用,一個引用指向堆內存中對象。
2)軟引用:還有用但非必須的,當空間不足了(即將OOM),才會對它進行回收。
3)弱引用:也是非必須的,不管空間剩餘多少,只要有垃圾回收就進行回收。
4)虛引用:一個對象是否有虛引用,不會對它的生存事件有任何影響,也無法通過虛引用獲得一個實例。唯一目的能在對象被回收時收到一個系統通知。
1)當對象被判定為不可達後,會進行一次標記,並篩選出覆蓋了finalize方法且還沒被執行過的對象進入下一步,那些沒有覆蓋的,或覆蓋但已執行過的(finalize只能執行一次)將會被回收。
2)將篩選出的對象加入一個隊列,並有一個優先順序很低的Finalier線程去執行隊列中對象的finalize方法,若finalize方法中該對象重新獲得了引用,則復活,否則在第二次標記時他將被回收(第二次標記就是第一步中的標記,循環這兩步)。
1)標記清除:將不可達的對象進行標記,GC時回收、---->效率低、空間碎片
2)復制:將內存區域(堆)分為兩塊,每次只使用其中的一塊。將可達對象進行標記,復制到另一邊,然後將剛才那一邊全部清空。
這中演算法適用於存活對象很少的情況,經常被用於新生代的垃圾回收。
3)標記整理:將不可達對象標記後,清除對象後將存活對象向一端移動,減少空間碎片的產生。
該演算法被廣泛使用於老年代中。
4)分代收集:由於不同的對象有自己不同的特點。將區域劃分為新生代與老年代。
新生代:對象大都朝生夕死,存活時間短。每次GC只有少量對象存活。一般用 復制演算法 。(復制演算法一般需要空間分配擔保空間)
老年代:對象存活時間較長。也沒有額外空間對他進行分配擔保,更適合標記清除 、標記整理演算法。
要注意垃圾回收器的設計目標是,有的是為了減少STW的時間,有的是為了吞吐量。這也應該作為一個選擇回收器的標准。
1)Serial / Serial Old
Serial是一個單線程的垃圾回收器,進行垃圾回收時,必然要STW。Serial在新生代採用復制演算法,其對應的老年代回收器在老年代採用標記整理法。
2)ParNew
ParNew實質上一個多線程的Serial,他能夠實現多個線程進行垃圾回收,但仍是需要用戶線程STW之後才能並發執行垃圾回收。一般開啟CPU核數個線程,用-XX:ParallelGCThreads設置。也可以用-XX:SurvivorRatio設置年輕代Eden與Survivor的比例,-XX:HandlerPromotionFailure設置空間分配擔保是否開啟。
是一種年輕代的垃圾回收器,採用復制演算法。
3)Parallel Scavenge收集器 /Parallel Old
這是一款新生代收集器,也是採用復制演算法。特點是關注點不在於停頓時間,而在於吞吐量。可通過時設置-XX:UseAdapatorSizePolicy為true將該收集器設為一個自適應調節策略,會自動根據吞吐量與停頓時間等因素進行自適應調節。
相關參數:設置吞吐量大小-XX: GCTimeRatio,-XX:MaxGCPauseMillis:設置最大垃圾收集停頓時間,-XX:UseAdaptativeSizePolicy自適應的設置新生代中區域的比例
Parallel Old是它的老年代的回收器,採用多線程和標記整理演算法實現,可以選擇Parallel Scavenge與Parallel Old配合使用組合為一個注重吞吐量的垃圾回收機制。
4)CMS (Concurrent Mark Sweep)老年代回收器!
由名字就可以看出來,是一款基於並發的標記清除演算法的收集器,CMS收集器是針對於老年帶的收集器,以最短停頓時間為目標。
STEP 分為四個步驟,其中耗時最長的並發標記與並發清除是與用戶線程並發完成的,而其它兩個步驟會有短暫的停頓,以此種方式盡量減少停頓時間
a.初始標記:就是標記一些GCRoots的直接引用,速度非常快,這一過程會有短暫的SWT
b.並發標記:與用戶線程並發的進行GCRoots tracing,沿引用鏈去標記不可達的對象。
c.重新標記:由於第二步是與用戶線程一起發生的,有可能在回收過程中會有新的垃圾產生,這一步就是對於這些浮動垃圾進行標記(短暫停頓)
d.並發清除:與用戶線程並發進行垃圾回收,採用標記清除演算法。
優缺點
並發收集、低停頓
a.對CPU資源敏感,降低吞吐量(吞吐量指用戶線程佔用時間/總運行時間,這種演算法佔用了用戶現成的一部分CPU時間)(並發涉及的程序都會爭搶CPU資源)
b.標記清除演算法導致的空間碎片:可能會造成大量空間浪費,若大對象(直接進入老年代)存儲時,則會由於連續空間不足引發FullGC。可通過設置-XX:UseCMSCompactAtFullCollection開關,設置是否在FullGC前執行碎片整理合並。
c.浮動垃圾:在並發階段,用戶也可能會隨時產生垃圾。在這里需要考慮另一個問題,由於是並發的,所以我們需要考慮用戶線程需要佔用一部分存儲空間,就不可能等到老年代快滿了再回收垃圾,需預留給用戶線程一些空間。可以通過設置-XX:來設置觸發FullGC時內存空間使用比例,若這個參數過低,則會增加發生GC的次數;若過高,留給用戶空間的內存不夠,又會引發Concurrent Mode Failure,而啟用Serial Old收集器作為備份方案。
5)G1(Garbage first)
特點
a.將新生代與年老代同一劃分為多個Region區域。
b.標記整理演算法。
c.與用戶線程並發執行。
d.可預測停頓時間。因為可以有計劃的避免在整個java堆中進行全局的回收,將堆劃分為多個Region,並為每個計算Region里垃圾堆積的價值大小(回收空間大小以及時間),根據價值維護一個Region的優先列表,每次都選取列表中第一個進行回收,即回收價值最大的那個Region。
需要考慮的問題是:化整為零後,多個region之間的一些引用如何確定呢(新生代老年代也有該問題),虛擬機採用Remember Set來避免全表掃描。每個region維護一個Remember Set,在對引用類型進行寫操作時,會發生寫中斷,此時檢查該引用的對象是否在別的Region中,若是,則將該信息寫入他自己所屬Region的RememberSet中。以便在對某一個Region進行可達性分析是不需要全表掃描。
STEP
a.初始標記:僅標記GCRoot是直接引用
b.並發標記:並發的標記所有不在引用鏈上的引用
c.最終標記:在並發標記階段新產生的對象會寫入Remember set log中,在該階段將Remember Set log中的數據更新進Remember set中,進行標記。
d.篩選回收:!!!不是並發的,對Region的價值進行排序,並根據用戶希望的GC停頓時間制定回收計劃,由於只回收一個Region,速度也很快,就沒有採取並發。
從前面已經知道了,我們根絕對象生存時間的長短不一這一特徵,將堆劃分為年輕代和年老代。這里進一步了解年輕代內部劃分,以及分配對象時區域之間是如何協助的,從而進一步能夠知道GC觸發的時間。
1)年輕代的劃分與基本工作機制
年輕代一般採用復制演算法,將年輕代劃分為Eden區與FromSurvivor、ToSurvivor。分配對象時,首先分配到Eden與FromSurvivor區中,然後將可達的對象復制到ToSurvivor區中,注意此時將這些對象的年齡+1,並清除Eden+FromS中的無用對象。然後再將FromS變為ToS,ToS變為FromS,(也省去了將To中的對象復制給From中這一步驟)以便下一次的對象回收。
2)對象分配的機制
a. 一般對象首先分配給Eden區,若空間不足則會觸發MinorGC。
b. 大對象會直接分配到年老代,若空間不足觸發FullGC。
c. 對象在幾個情況下將從年輕代進入年老代:
c1:對象年齡到了(默認為15,在復制過程中年齡增加,因為可達而熬過了一次GC嘛)可通過-XX:MaxTenuringThreshole設置進入年老代的年齡。
c2:動態對象年齡判斷:當年輕代中同一年齡的對象所佔存儲空間之和大於 Survivor 的一半時(占滿了toSurvivor?),將大於等於該年齡的對象都放入年老代
c3:空間分配擔保:年輕代採用復制演算法,就需要有空間在survivor區裝不下存活對象的時候幫忙存儲多出來的對象(一般發生於一次MinorGC之前,存活對象過多導致survivor放不下了),年老代即為空間分配擔保的空間。發生空間分配擔保時,會進入老年代。
3)空間分配擔保機制
年老代進行空間分配擔保是有風險的,若擔保過來的對象超過了剩餘空間,那麼年老代會發生FullGC。
所以在發生MinorGC之前,虛擬機會判斷新生代所有對象總空間(!!一次保障性的對比,若成立則其子集肯定成立)是否小於年老代最大連續空間大小,若小於,則擔保成功,認為這次MinorGC是安全的,執行GC。若大於,則要判斷年老代是否開啟HandlePromotionFailure(是否允許空間分配擔保),若沒開啟,則直接執行FullGC。若開啟了,則判斷以往平均擔保大小是否小於最大連續空間大小,若小於,則嘗試MinorGC(有風險,失敗了再FullGC,多一次MinorGC),若大於,則FullGC(包括MinorGC)。
4)總結一下GC觸發條件與設置
MinGC:當Eden區不夠時,可設置-XX:SurvivorRatio設置年輕代中Eden的比例,-XX:NewRatio設置堆中年輕代比例,-Xms -Xmx設置堆最小最大值。
FullGC:a.System.gc()
b.永久代,類、靜態變數,常量太多,不夠放 -XX:MaxPermSize
c.老年代:c1大對象直接進入時不夠 c2空間分配擔保,MinorGC前,不願意擔保直接FullGC,願意擔保且空間小於之前擔保平均值FullGC,若大於但擔保失敗了(這次太多了)先MinorGC(發現不行)再FullGC。
d,CMS回收器:只針對老年代收集,浮動垃圾過多,當超過設置的比例大小時(因為用戶線程也有空間),會FullGC。設置老年代空間使用比例
-XX:,到達這么多時FullGC
目前了解的調優方式:
1)頻繁GC時,要使用JDK一些工具排查問題具體位置。
jstat:虛擬機各方面的運行數據 jmap:生成內存轉儲快照 一般通過這兩個就能定位堆中的問題,判斷是空間設置不合理還是程序問題。
跟著GC出發點走,調整各個區域大小-Xms -Xmx -XNewRatio -XX:SurvivorRatio -XX:permSize,以及年老代相關的 maxTenuringTreshold,handlePromotionFailure(一般設為true,以減少FullGC,給MinorGC機會嘛)
2)性能問題,要根據需求選擇合適的垃圾回收器,以停頓時間為目標還是以吞吐量為目標
⑶ 從垃圾回收GCDetails看JVM GC原理
1.啟動參數
2.回收原理
為了更好的理解GCDetails信息回憶下新生代回收的演算法(圖出自網友),此處不會對回收演算法進行詳細的講解,也不會介紹ParallelGC的XX:MaxGCPauseMilli、XX:GCTimeRatio等參數。
3.GCDetails信息
4.分析
Parallel Scavenge是JDK8默認的新生代垃圾回收演算法,是一種以吞吐量為目標的垃圾回收器,基於標記復制演算法,內存分布為堆內存中劃分了2塊區域,一塊為新生代,一塊為老年代,默認配置新生代占堆內存1/3,老年代占堆內存2/3,新生代分為Eden,Survivor_To,Survivor_From,默認分配比例為8:1:1,Survivor區負責存儲垃圾回收未能回收的對象和晉升到老年代的操作,從上面的GCDetails可以分析一下垃圾回收的大概原理。
以第一行為例,131584K為新生代回收前佔用內存大小,21503K為新生代回收後佔用內存大小,可以得知這一次GC回收了約110M,垃圾回收器使用Parallel Scavenge,131584K為整個堆內存回收前內存佔用大小,42320K為整個堆內存這次GC後內存佔用的大小,可以得知這一次GC回收了約89M內存,垃圾回收器使用Parallel Old,花費了大約8.3毫秒,那新生代回收內存與整個堆回收的內存大小差就是新生代晉升到老年代的內存大小,可以得知為21M。
以第二次GC為例,可以看到他觸發了Full GC,原因很簡單,就是老年代的空間不足,在這次GC中,新生代回收了16269K內存空間,OK代表新生代全部內存都已經回收,老年代回收了316390K-236940K內存空間,大約80M,整個堆內存回收了333077K-271355K內存空間,大約61M,可以得知新生代晉升80-61內存空間,約19M。
在過往的生產經驗中只要JVM或者程序沒有相關的告警,其實很少去調整啟動參數,通過GC日誌的學習與分析,我們發現堆內存的大小與系統的吞吐量還是有直接的關系,大家可以嘗試使用不同的-Xms、-Xmx1參數來測試系統的吞吐量,我們會發現如果內存過小,會頻繁的發生年輕代GC甚至會頻發的一直發生Full GC或內存泄漏,其實內存離譜的大也會影響性能,老年代Full GC時STW(stop the word)的時間會比較久,所以一個合適的啟動參數是在性能測試中必不可少的一環。
5.垃圾回收器選擇參數
在新生代都是Parallel Scavenge回收器的時候,本來想測試一下配合不同的老年代演算法來看一下性能,准備使用XX:+UseParallelGC、XX:+UseParallelOldGC,但是發現老年代回收演算法一直都是ParOldGen,困惑了一段時間後通過搜索資料發現在JDK 7u4以後的7和JDK8,新生代演算法為Parallel Scavenge,老年代默認都為Parallel Old。
6.一點疑問
上面的GC日誌中,其中有兩個括弧,如第一行的(153088K)和(502784K)分別代表新生代分配的內存大小與堆內存分配的大小,但是我們發現這個大小並不是固定的,在七次乃至後面GC的時候括弧內的大小發生了改變,這是為什麼呢,其實JDK8 Parallel Scavenge默認開啟了-XX:+ UseAdaptiveSizePolicy 參數,這個參數會根據吞吐量與垃圾回收的時間動態的調整各內存區域的大小,雖然有默認新生代的分配比例SurvivorRatio=8,但是也不會固定大小,如果在啟動參數顯示的寫SurvivorRatio=8,則分配的內存不會動態調整,大家可以動手實驗一下。
在JD8以前新生代垃圾回收除了Parallel Scavenge還有Serial和ParNew大家可以通過以下的指令進行測試,都基於標記復制演算法,GC日誌在新生代部分都是相同的分析方法。
1.啟動參數
2.回收原理
CMS作為一款老年代垃圾回收期,以減少垃圾回收的停頓時間為目標,採用標記清除演算法,只能與新生代垃圾回收ParNew和Serial搭配使用,JDK9後只支持與ParNew配合使用,同樣本部分不做詳細的原理的說明,主要是分析GCDetails日誌來加深理解,這部分有兩個比較重要的概念,一個是三色標記法與讀寫屏障,一個是卡表結構,沒有了解的可以參考 這篇文章 ,感覺寫的比較好理解。
3.GCDetails日誌
4.分析
GC (Allocation Failure)是使用ParNew作為新生代的垃圾回收演算法,此部分的內存情況與之前分析Parallel相似,所以這部分主要分析CMS老年代的垃圾回收原理。CMS垃圾回收日誌其實與垃圾回收的階段強相關,主要也是通過看日誌來反向加強垃圾回收的流程與原理。
1)初始標記-CMS Initial Mark
CMS為了減少停頓時間大家可以記兩段標記階段都STW,剩下的階段都並行,日誌中時間0.328行可以得知一些信息,初始標記階段老年代使用內存大小為203782K,整個老年代分配大小為349568K,整個堆內存使用內存為221541K,整個堆內存分配的大小為506816K,標記的時間也很短,從這里也能大概算出新生代分配的內存大小約為506816K-349568K=157M左右,與第一行的新生代內存分配大小一致。
2)並發標記-CMS-concurrent-mark,並發標記主要是根據初始標記來進行對象的可達性,日誌中時間0.330行得知並行標記花費了0.01s;預清理-CMS-concurrent-preclean,預清理主要解決在並發標記的過程中業務線程對對象引用關系的修改(包括跨代引用和引用改變),主要是掃描標記為「臟塊」的卡表,花費了0.01s;
3)可終止預清理-CMS-concurrent-abortable-preclean,該步驟不是一定執行,如果現在新生代內存小於則不執行,如果大於該值繼續標記(一直重復)的時候會有退出條件如循環次數、時間閾值、Eden區內存使用率等,在循環退出之前會進行一次YGC減輕後面最終標記的壓力(因為老年代存在指向年輕代的指針),通過日誌我們可以看出在0.331的可終止預清理階段進行了三次YGC,從內存的情況上看 有大量對象從新生代晉升到老年代。
小疑問:在該部分我們觀察時間指標是0.003/0.136 secs,0.003s是CPU執行的時間,0.136s是可終止預清理的總耗時,他們之間存在一個差值,這部分我其實也沒有一個明確的答案,我第一感覺可能是在等待一次新生代GC。
3)最終標記-CMS Final Remark
從日誌中時間0.468可以分析最終標記的結果,17759K新生代使用內存的大小,Rescan (parallel)為最後的標記SWT的時間,weak refs processing為清理弱引用的時間,class unloading為卸載未使用類的時間,scrub symbol table為清理符號表,我理解就是清理符號引用,scrub string table為清理字元表,我理解就是清理字面量,最終CMS的最終標記後老年代使用內存為334051K,堆內存使用為351810K。
4)並發清理-CMS-concurrent-sweep,很好理解就是並發清理;並發重置-CMS-concurrent-reset,重新調整CMS的內存結構,以便下次垃圾回收。
5.調優
在CMS中不是並發的垃圾回收一定成功,可能會發生失敗,這樣就會降級為Serial Old垃圾回收演算法,為了避免這個可以考慮參數,默認92%
⑷ JVM垃圾收集機制
JVM垃圾回收機制是java程序員必須要了解的知識,對於程序調優具有很大的幫助(同時也是大廠面試必問題)。
要了解垃圾回收機制,主要從三個方面:
(1)垃圾回收面向的對象是誰?
(2)垃圾回收演算法有哪些?
(3)垃圾收集器有哪些?每個收集器有什麼特點。
接下來一一講解清楚:
一、垃圾回收面向的對象
也就是字面意思, 垃圾 回收嘛,重要的是垃圾,那什麼對象是垃圾呢,簡單來說就是無用的或已死的對象。這樣又引申出來什麼對象是已死的,怎麼判斷對象是否已死?
判斷對象是否已死有兩種演算法和對象引用分類:
(1)引用計數演算法:
也是字面意思,通過給對象添加引用計數器,添加引用+1,反之-1。當引用為0的時候,此時對象就可判斷為無用的。
優點:實現簡單,效率高。
缺點:無法解決循環引用(就是A引用B,B也引用A的情況)的問題。
(2)根搜索演算法(也就是GC Roots):
通過一系列稱為GC Roots的對象,向下搜索,路徑為引用鏈,當某個對象無法向上搜索到GC Roots,也就是成為GC Roots不可達,則為無用對象。
如果一個對象是GC Roots不可達,則需要經過兩次標記才會進行回收,第一次標記的時候,會判斷是否需要執行finalize方法(沒必要執行的情況:沒實現finalize方法或者已經執行過)。如果需要執行finalize方法,則會放入一個回收隊列中,對於回收隊列中的對象,如果執行finalize方法之後,沒法將對象重新跟GC Roots進行關聯,則會進行回收。
很抽象,對吧,來一個明了的解釋?
比如手機壞了(不可達對象),有錢不在乎就直接拿去回收(這就是沒實現finalize方法),如果已經修過但是修不好了(已經執行過finalize方法),就直接拿去回收站回收掉。如果沒修過,就會拿去維修店(回收隊列)進行維修,實在維修不好了(執行了finalize方法,但是無法連上GC Roots),就會拿去回收站回收掉了。
那什麼對象可以成為GC Roots呢?
1>虛擬機棧中的引用對象
2>本地方法棧中Native方法引用的對象
2>方法區靜態屬性引用對象
3>方法區常量引用對象
(3)對象引用分類
1>強引用:例如實例一個對象,就是即使內存不夠用了,打死都不回收的那種。
2>軟引用:有用非必須對象,內存夠,則不進行回收,內存不夠,則回收。例如A借錢給B,當A還有錢的時候,B可以先不還,A沒錢了,B就必須還了。
3>弱引用:非必須對象,只能存活到下一次垃圾回收前。
4>虛引用:幽靈引用,必須跟引用隊列配合使用,目的是回收前收到系統通知。
下面是java的引用類型結構圖:
(1)軟引用示例
內存夠用的情況:
運行結果:
內存不夠用的情況:
運行結果:
(2)弱引用示例結果:
無論如何都會被回收
(3)虛引用示例:
運行結果:
解釋:為什麼2和5的輸出為null呢,如下
3為null是因為還沒有進行gc,所以對象還沒加入到引用隊列中,在gc後就加入到了引用隊列中,所以6有值。
這個虛引用在GC後會將對象放到引用隊列中,所以可以在對象回收後做相應的操作,判斷對象是否在引用隊列中,可以進行後置通知,類似spring aop的後置通知。
二、垃圾回收發生的區域
垃圾回收主要發生在堆內存裡面,而堆內存又細分為 年輕代 和 老年代 ,默認情況下年輕代和老年代比例為1:2,比如整個堆內存大小為3G,年輕代和老年代分別就是1G和2G,想要更改這個比例需要修改JVM參數-XX:NewRatio,
比如-XX:NewRatio=4,那老年代:年輕代=4:1。而年輕代又分為Eden區,S0(Survivor From)和S1(Survivor To)區,一般Eden:S0:S1=8:1:1,如果想要更改此比例,則修改JVM參數-XX:SurvivorRatio=4,此時就是Eden:S0:S1=4:1:1。
在年輕代發生GC稱為Young GC,老年代發生GC成為Full GC,Young GC比Full GC頻繁。
解析Young GC:
JVM啟動後,第一次GC,就會把Eden區存活的對象移入S0區;第二次GC就是Eden區和S0一起GC,此時會把存活的對象移入S1區,S0清空;第三次GC就是Eden區和S1區進行GC,會把存活的對象移入S0區,如此往復循環15次(默認),就會把存活的對象存入老年區。
類似與如果有三個桶,編號分別為1(1號桶內的沙子是源源不斷的,就像工地上。你們沒去工地搬過磚可能不知道,但是我真的去工地上搬過啊),2,3。1裡面裝有沙子,需要將沙子篩為細沙。首先將桶1內的沙子篩選一遍過後的放置於桶2,第二次篩選就會將桶1和桶2裡面的沙子一起篩,篩完之後放到桶3內,桶2清空。第三次篩選就會將桶1和桶3的沙子一起篩選,曬完放到桶2內,桶3清空。如此往復循環15次,桶2或桶3裡面的沙子就是合格的沙子,就需要放到備用桶內以待使用。
上述中桶1就是Eden區,桶2就是S0區,桶3就是S1區。
三、垃圾回收演算法
三種,分別是復制演算法,標記-清除演算法,標記-整理演算法。
(1)復制演算法。
其會將內存區域分成同樣大小的兩塊,一塊用來使用,另外一塊在GC的時候存放存活的對象,然後將使用的一塊清除。如此循環往復。
適用於新生代。
優點:沒有內存碎片,缺點:只能使用一般的內存。
(2)標記-清除演算法。
使用所有內存區域,在GC的時候會將需要回收的內存區域先進行標記,然後同意回收。
適用於老年代。
缺點:產生大量內存碎片,會直接導致大對象無法分配內存。
(3)標記-整理演算法。
使用所有內存區域,在GC的時候會先將需要回收的內存區域進行標記,然後將存活對象忘一邊移動,最後將清理掉邊界以外的所有內存。
適用於老年代。
四、GC日誌查看
利用JVM參數-XX:+PrintGCDetails就可以在GC的時候列印出GC日誌。
年輕代GC日誌:
老年代GC日誌:
五、垃圾收集器
主要有四類收集器以及七大收集器
四類:
(1)Serial:單線程收集器,阻塞工作線程,它一工作,全部都得停下。
(2)Paralle:Serial的多線程版本,也是阻塞工作線程
(3)CMS(ConcMarkSweep):並行垃圾收集器,可以和工作線程一起工作。
(4)G1:將堆分成大小一致的區域,然後並發的對其進行垃圾回收。
怎麼查看默認的收集器呢?
用JVM參數-XX:+PrintCommandLineFlags,運行之後會輸出如下參數。可以看到,jdk1.8默認是Parallel收集器。
七大收集器:
(1)Serial:串列垃圾收集器,單線程收集器。用於新生代。用JVM參數-XX:+UseSerialGC開啟,開啟後Young區用Serial(底層復制演算法),Old區用Serial Old(Serial的老年代版本,底層是標記整理演算法)。
(2)ParNew:用於新生代,並行收集器。就是Serial的多線程版本。用JVM參數-XX:+UseParNewGC,young:parnew,復制演算法。Old:serialOld,標記整理演算法。-XX:ParallecGCThreads限制線程回收數量,默認跟cpu數目一樣。只是新生代用並行,老年代用串列。
(3)Parallel Scavenge:並行回收收集器。用JVM參數-XX:+UseParallelGC開啟,young:parallel scavenge(底層是復制演算法),old:parallel old(parallel的老年代版本,底層是標記整理),新生代老年代都用並行回收器。
這個收集器有兩個優點:
可控的吞吐量 :就是工作線程工作90%的時間,回收線程工作10%的時間,即是說有90%的吞吐量。
自適應調節策略 :會動態調節參數以獲取最短的停頓時間。
(4)Parallel Old:Parallel Scavenge的老年代版本,用的是標記整理演算法。用JVM參數-XX:+UseParallelOldGC開啟,新生代用Parallel Scavenge,老年代用Parallel Old
(5)CMS(ConcMarkSweep):並發標記清除。 底層是標記清除演算法,所以會產生內存碎片,同時也會耗cpu。 以獲取最短回收停頓時間為目標。-XX:+UseConcMarkSweep,新生代用ParNew,老年代用CMS。CMS必須在堆內存用完之前進行清除,否則會失敗,這時會調用SerialOld後備收集器。
初始標記和重新標記都會停止工作線程,並發標記和並發清除會跟工作線程一起工作。
(6)SerialOld:老年代串列收集器(以後Hotspot虛擬機會直接移除掉)。
(7)G1:G1垃圾收集器,演算法是標記整理,不會產生內存碎片。橫跨新生代老年代。實現盡量高吞吐量,滿足回收停頓時間更短。
G1可以精確控制垃圾收集的停頓時間,用JVM參數-XX:MaxGCPauseMillis=n,n為停頓時間,單位為毫秒。
區域化內存劃片Region,會把整個堆劃分成同樣大小的區域塊(1MB~32MB),最多2048個內存區域塊,所以能支持的最大內存為32*2048=65535MB,約為64G。
上圖是收集前和收集後的對比,有些對象很大,分割之後就是連續的區域,也即是上圖的Humongous。
上述理論可能有點乏味,下圖很清晰明了(某度找的)。
下面來一張整個垃圾回收機制的思維導圖(太大,分成兩部分)。
=======================================================
我是Liusy,一個喜歡健身的程序猿。
歡迎關注【Liusy01】,一起交流Java技術及健身,獲取更多干貨。
⑸ JVM垃圾回收的「三色標記演算法」實現,內容太干
三色標記法是一種垃圾回收法,它可以讓JVM不發生或僅短時間發生STW(Stop The World),從而達到清除JVM內存垃圾的目的。JVM中的 CMS、G1垃圾回收器 所使用垃圾回收演算法即為三色標記法。
三色標記法將對象的顏色分為了黑、灰、白,三種顏色。
白色 :該對象沒有被標記過。(對象垃圾)
灰色 :該對象已經被標記過了,但該對象下的屬性沒有全被標記完。(GC需要從此對象中去尋找垃圾)
黑色 :該對象已經被標記過了,且該對象下的屬性也全部都被標記過了。(程序所需要的對象)
從我們main方法的根對象(JVM中稱為GC Root)開始沿著他們的對象向下查找,用黑灰白的規則,標記出所有跟GC Root相連接的對象,掃描一遍結束後,一般需要進行一次短暫的STW(Stop The World),再次進行掃描,此時因為黑色對象的屬性都也已經被標記過了,所以只需找出灰色對象並順著繼續往下標記(且因為大部分的標記工作已經在第一次並發的時候發生了,所以灰色對象數量會很少,標記時間也會短很多), 此時程序繼續執行,GC線程掃描所有的內存,找出掃描之後依舊被標記為白色的對象(垃圾),清除。
具體流程:
在JVM虛擬機中有兩種常見垃圾回收器使用了該演算法:CMS(Concurrent Mark Sweep)、G1(Garbage First) ,為了解決三色標記法對對象漏標問題各自有各自的法:
CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間為目標的收集器。目前很大一部分的Java應用集中在互聯網網站或者基於瀏覽器的B/S系統的服務端上,這類應用通常都會較為關注服務的響應速度,希望系統停頓時間盡可能短,以給用戶帶來良好的交互體驗。CMS收集器就非常符合這類應用的需求(但是實際由於某些問題,很少有使用CMS作為主要垃圾回收器的)。
從名字(包含「Mark Sweep」)上就可以看出CMS收集器是基於標記-清除演算法實現的,它的運作過程相對於前面幾種收集器來說要更復雜一些,整個過程分為四個步驟,包括:1)初始標記(CMS initial mark) 2)並發標記(CMS concurrent mark) 3)重新標記(CMS remark) 4)並發清除(CMS concurrent sweep)
其中初始標記、重新標記這兩個步驟仍然需要「Stop The World」。初始標記僅僅只是標記一下GCRoots能直接關聯到的對象,速度很快;
並發標記階段就是從GC Roots的直接關聯對象開始遍歷整個對象圖的過程,這個過程耗時較長但是不需要停頓用戶線程,可以與垃圾收集線程一起並發運行;
重新標記階段則是為了修正並發標記期間,因用戶程序繼續運作而導致標記產生變動的那一部分對象的標記記錄,這個階段的停頓時間通常會比初始標記階段稍長一些,但也遠比並發標記階段的時間短;
最後是並發清除階段,清理刪除掉標記階段判斷的已經死亡的對象,由於不需要移動存活對象,所以這個階段也是可以與用戶線程同時並發的。由於在整個過程中耗時最長的並發標記和並發清除階段中,垃圾收集器線程都可以與用戶線程一起工作,所以從總體上來說,CMS收集器的內存回收過程是與用戶線程一起並發執行的。
在應對漏標問題時,CMS使用了增量更新(Increment Update)方法來做:
在一個未被標記的對象(白色對象)被重新引用後, 引用它的對象若為黑色則要變成灰色,在下次二次標記時讓GC線程繼續標記它的屬性對象 。
但是就算是這樣,其仍然是存在漏標的問題:
G1(Garbage First)物理內存不再分代,而是由一塊一塊的Region組成,但是邏輯分代仍然存在。G1不再堅持固定大小以及固定數量的分代區域劃分,而是把連續的Java堆劃分為多個大小相等的獨立區域(Region),每一個Region都可以根據需要,扮演新生代的Eden空間、Survivor空間,或者老年代空間。收集器能夠對扮演不同角色的Region採用不同的策略去處理,這樣無論是新創建的對象還是已經存活了一段時間、熬過多次收集的舊對象都能獲取很好的收集效果。
Region中還有一類特殊的Humongous區域,專門用來存儲大對象。G1認為只要大小超過了一個Region容量一半的對象即可判定為大對象。每個Region的大小可以通過參數-XX:G1HeapRegionSize設定,取值范圍為1MB~32MB,且應為2的N次冪。而對於那些超過了整個Region容量的超級大對象,將會被存放在N個連續的Humongous Region之中,G1的大多數行為都把Humongous Region作為老年代的一部分來進行看待,如圖所示
Card Table(多種垃圾回收器均具備)
RSet(Remembered Set)
是輔助GC過程的一種結構,典型的空間換時間工具,和Card Table有些類似。
後面說到的CSet(Collection Set)也是輔助GC的,它記錄了GC要收集的Region集合,集合里的Region可以是任意年代的。
在GC的時候,對於old->young和old->old的跨代對象引用,只要掃描對應的CSet中的RSet即可。邏輯上說每個Region都有一個RSet,RSet記錄了其他Region中的對象引用本Region中對象的關系,屬於points-into結構(誰引用了我的對象)。
而Card Table則是一種points-out(我引用了誰的對象)的結構,每個Card 覆蓋一定范圍的Heap(一般為512Bytes)。G1的RSet是在Card Table的基礎上實現的:每個Region會記錄下別的Region有指向自己的指針,並標記這些指針分別在哪些Card的范圍內。這個RSet其實是一個Hash Table,Key是別的Region的起始地址,Value是一個集合,裡面的元素是Card Table的Index。每個Region中都有一個RSet,記錄其他Region到本Region的引用信息;使得垃圾回收器不需要掃描整個堆找到誰引用當前分區中的對象,只需要掃描RSet即可。
CSet(Collection Set)
一組可被回收的分區Region的集合, 是多個對象的集合內存區域。
新生代與老年代的比例
5% - 60%,一般不使用手工指定,因為這是G1預測停頓時間的基準,這地方簡要說明一下,G1可以指定一個預期的停頓時間,然後G1會根據你設定的時間來動態調整年輕代的比例,例如時間長,就將年輕代比例調小,讓YGC盡早行。
SATB(Snapshot At The Beginning), 在應對漏標問題時,G1使用了SATB方法來做,具體流程:
因為SATB在重新標記環節只需要去重新掃描那些被推到堆棧中的引用,並配合Rset來判斷當前對象是否被引用來進行回收;
並且在最後G1並不會選擇回收所有垃圾對象,而是根據Region的垃圾多少來判斷與預估回收價值(指回收的垃圾與回收的STW時間的一個預估值),將一個或者多個Region放到CSet中,最後將這些Region中的存活對象壓縮並復制到新的Region中,清空原來的Region。
會,當內存滿了的時候就會進行Full GC;且JDK10之前的Full GC,為單線程的,所以使用G1需要避免Full GC的產生。
解決方案:
⑹ JVM的垃圾演算法有哪幾種
一、垃圾收集器概述
如上圖所示,垃圾回收演算法一共有7個,3個屬於年輕代、三個屬於年老代,G1屬於橫跨年輕代和年老代的演算法。
JVM會從年輕代和年老代各選出一個演算法進行組合,連線表示哪些演算法可以組合使用
二、各個垃圾收集器說明
1、Serial(年輕代)
年輕代收集器,可以和Serial Old、CMS組合使用
採用復制演算法
使用單線程進行垃圾回收,回收時會導致Stop The World,用戶進程停止
client模式年輕代默認演算法
GC日誌關鍵字:DefNew(Default New Generation)
圖示(Serial+Serial Old)
7、G1
G1收集器由於沒有使用過,所以從網上找了一些教程供大家了解
並行與並發
分代收集
空間整合
可預測的停頓
⑺ jvm堆內存和非堆內存(小白入門文,各博客視頻基礎總結)
一:堆內存和非堆內存定義
Java虛擬機具有一個堆(Heap),堆是運行時數據區域,所有類實例和數組的內存均從此處分配。堆是Java虛擬機啟動時創建的。在JVM中堆之外的內u你成為非堆內存(Non-heap memory)。
堆內存以及相應垃圾回收演算法
1.堆的大小可以固定,也可以擴大和縮小,堆內存不需要是連續空間。
2.對象創建後進入Eden。年輕代分為Eden和Survivor。Survivor由FromSpace和ToSpace組成。Eden區佔大容量,Survivor佔小容量,默認比例8:1:1。
MinorGC:採用復制演算法。首先把Eden和ServivorFrom區域中存活的對象賦值到ServivorTo區域(如果對象年齡達到老年標准/ServivorTo位置不夠了,則復制到老年代),同時對象年齡+1,然後清空Eden和ServivorFrom中的對象。然後ServivorTo和ServivorFrom互換。
3.老年代
老年代存放生命周期長的內存對象。
老年代對象相對穩定,所以不會頻繁GC。在進行MajorGC前一般都先進行一次MinorGC,使新生代的對象進入老年代,導致空間不夠用時才觸發。當無法找到足夠大的連續空間分配給新晉的對象也會提前觸發MajorGC進行垃圾回收。
MajorGC:如果使用CMS收集器,採用標記-清除演算法。首先掃描老年代,標記所有可回收對象,標記完成後統一回收所有被標記對象。同時會產生不連續的內存碎片。碎片過多會導致以後程序運行需要分配較大對象時,無法找到足夠的連續內存,而不得已再次出發GC。否則採用標記-壓縮演算法。
標記-壓縮:在標記可回收對象後,將不可回收對象移向一端,然後清除標記對象。
當老年代也滿了裝不下時,拋出OOM異常。
二:永久代
內存中永久保存的區域,主要存放Class和Meta(元數據)的信息,Class在被載入的時候被放入永久區域。他和存放實例的區域不同,GC不會再主程序運行期對永久區進行清理。所以也可可能導致永久代區域隨著載入Class的增多而脹滿,拋出OOM。
Java8中,永久代已經被移除,被一個成為「元數據區」(元空間)的區域所取代。
元空間的本質與永久代類似,都是JVM方法區的實現。不過元空間使用本地內存,永久代在JVM虛擬機中。因此,默認情況下,元空間的大小受本地內存限制。類的元數據放入native memory,字元串常量池和類的靜態變數放入java堆中,這樣可以載入多少類的元數據就不再由MaxPermSize控制,而是由系統實際可用空間控制。
1元空間解決了永久代的OOM問題,元數據和class對象在永久代容易出現性能問題和內存溢出。
2類的方法信息等比較難確定其大小,對於永久代的大小指定比較困難,小永久代溢出,大老年代溢出。
3永久代會為GC帶來不必要的復雜度,回收效率低。
三:堆內存參數調優
1.-Xms 設置初始分配內存大小,默認物理內存1/64
2.-Xmx 設置最大分配內存,默認物理內存1/4
long maxMemory = Runtime.getRuntime().maxMemory(); long totalMemory = Runtime.getRuntime().totalMemory(); System.out.println("最大分配內存"+maxMemory/(double)1024/1024+"MB "+maxMemory/(double)1024/1024/1024+"GB"); System.out.println("默認分配內存"+totalMemory/(double)1024/1024+"MB "+totalMemory/(double)1024/1024/1024+"GB");
⑻ JVM有哪些垃圾回收演算法
標記-清除,標記-復制,標記-整理
⑼ 你不得不知道的JVM 垃圾回收
一、四種引用方式
1.1 強引用
1.2 軟引用(SoftReference)
1.3 弱引用(WeakReference)
1.4 虛引用(PhantomReference)
二、如何判斷對象是垃圾
2.1 引用計數法
2.2 根可達性分析
三、垃圾回收演算法
3.1 標記-清除(mark-sweep)
3.2 標記-整理(mark-compact)
3.3 標記-復制(mark-)
四、垃圾收集器
4.1 分類及特點簡述
4.1.1 串列
4.1.2 吞吐量優先
4.1.3 響應時間優先
4.2 串列垃圾回收器詳述
4.2.1 Serial
4.2.2 Serial-Old
4.2.3 流程圖
4.3 吞吐量優先垃圾回收器詳述
4.3.1 JVM相關參數
4.3.2 流程圖
4.4、響應時間優先垃圾回收器詳述
4.4.1 JVM相關參數
4.4.2 流程圖
4.3.3 CMS的特點
五、G1垃圾回收器
5.1 相關JVM參數
5.2 特點
5.3 G1新生代垃圾回收
5.4 G1老年代垃圾回收
只有所有 GC Roots對象都不通過【強引用】引用該對象,該對象才可以被回收。
某個對象只要有一處引用關系,該對象的引用次數就加1,如果一個對象的引用次數為0,則說明該對象是垃圾。
優勢:實現簡單,效率較高
弊端:如果有一對對象之間形成了相互引用,但是這兩個對象都已經沒有被其它對象所引用了,正常情況下,這一對對象應該被作為垃圾回收掉,但是因為形成了相互引用導致無法被回收。
通過GC Root對象開始向下尋找,尋找不到的對象即說明沒有被引用,那麼這些沒有被引用的對象被認定為垃圾。
目前,如下對象可以作為GC Root對象:
很好理解,即在GC的放生時候,先對所有對象進行根可達性分析,藉此標記所有的垃圾對象;所有對象標記完畢之後會進行清理操作。
因此,總體來說,就是先標記再清除。
弊端;標記清除之後會產生大量不連續的內存碎片,碎片太多可能會導致程序運行過程中需要分配較大對象時,無法滿足分配要求導致GC操作。
該回收演算法操作過程基本等同於 標記-清除 演算法只不過,第二步有點區別,該種方式會在清除的過程中進行 整理 操作,這是最大的不同。
優勢:最終不會出現若干空間碎片而導致的空間浪費。
弊端:在整理過程中帶來的計算不可小覷。
該種方式與前兩種有較大的區別:
該種方式會將存儲區分成兩個部分,分別為From、To,其中From區域中可能存在著對象,而To區域始終為空,用做下一次接受數據做准備。
分別有兩個指針指向這兩個區域:From-Pointer、To-Pointer,
優點:這種演算法非常適合早生夕死的對象
缺點:始終有一塊內存區域是未使用的,造成空間的浪費。
特點:
特點:
特點:
JVM開關:-XX:+UseSerialGC = Serial + SerialOld
上圖是:CMS垃圾回收器在老年代GC的工作流程圖:
經過上面的文字分析,新生代的Region個數為所有Region個數的5%;這個數值其實是很小的,那麼當新生代Region不夠用的時候,JVM會劃分更多的Region個數給新生代;
當新生代的Region個數佔比所有Region個數超過 60% 時,就會進行一次新生代的垃圾回收。
新生代垃圾回收會造成STW。
具體的垃圾回收演算法同其它幾個新生代垃圾回收器一樣,新生代都使用復制演算法。
老年代垃圾回收觸發機制與參數-XX:InitaingHeapOccupancyPercent有關。
但是需要注意的是:這一次的老年代回收,其實是一次混合垃圾回收,會同時清理新生代、老年代、Humongous。
與新生代回收演算法一致,依然使用復制演算法,但是垃圾回收的過程等同於老年代響應時間優先的CMS方式
流程分為:
⑽ jvm的垃圾回收機制詳解
1.JVM的gc概述
gc即垃圾收集機制是指jvm用於釋放那些不再使用的對象所佔用的內存。java語言並不要求jvm有gc,也沒有規定gc如何工作。不過常用的jvm都有gc,而且大多數gc都使用類似的演算法管理內存和執行收集操作。
在充分理解了垃圾收集演算法和執行過程後,才能有效的優化它的性能。有些垃圾收集專用於特殊的應用程序。比如,實時應用程序主要是為了避免垃圾收集中斷,而大多數OLTP應用程序則注重整體效率。理解了應用程序的工作負荷和jvm支持的垃圾收集演算法,便可以進行優化配置垃圾收集器。
垃圾收集的目的在於清除不再使用的對象。gc通過確定對象是否被活動對象引用來確定是否收集該對象。gc首先要判斷該對象是否是時候可以收集。兩種常用的方法是引用計數和對象引用遍歷。
1.1.引用計數
引用計數存儲對特定對象的所有引用數,也就是說,當應用程序創建引用以及引用超出范圍時,jvm必須適當增減引用數。當某對象的引用數為0時,便可以進行垃圾收集。
1.2.對象引用遍歷
早期的jvm使用引用計數,現在大多數jvm採用對象引用遍歷。對象引用遍歷從一組對象開始,沿著整個對象圖上的每條鏈接,遞歸確定可到達(reachable)的對象。如果某對象不能從這些根對象的一個(至少一個)到達,則將它作為垃圾收集。在對象遍歷階段,gc必須記住哪些對象可以到達,以便刪除不可到達的對象,這稱為標記(marking)對象。
下一步,gc要刪除不可到達的對象。刪除時,有些gc只是簡單的掃描堆棧,刪除未標記的未標記的對象,並釋放它們的內存以生成新的對象,這叫做清除(sweeping)。這種方法的問題在於內存會分成好多小段,而它們不足以用於新的對象,但是組合起來卻很大。因此,許多gc可以重新組織內存中的對象,並進行壓縮(compact),形成可利用的空間。
為此,gc需要停止其他的活動活動。這種方法意味著所有與應用程序相關的工作停止,只有gc運行。結果,在響應期間增減了許多混雜請求。另外,更復雜的 gc不斷增加或同時運行以減少或者清除應用程序的中斷。有的gc使用單線程完成這項工作,有的則採用多線程以增加效率。
2.幾種垃圾回收機制
2.1.標記-清除收集器
這種收集器首先遍歷對象圖並標記可到達的對象,然後掃描堆棧以尋找未標記對象並釋放它們的內存。這種收集器一般使用單線程工作並停止其他操作。
2.2.標記-壓縮收集器
有時也叫標記-清除-壓縮收集器,與標記-清除收集器有相同的標記階段。在第二階段,則把標記對象復制到堆棧的新域中以便壓縮堆棧。這種收集器也停止其他操作。
2.3.復制收集器
這種收集器將堆棧分為兩個域,常稱為半空間。每次僅使用一半的空間,jvm生成的新對象則放在另一半空間中。gc運行時,它把可到達對象復制到另一半空間,從而壓縮了堆棧。這種方法適用於短生存期的對象,持續復制長生存期的對象則導致效率降低。
2.4.增量收集器
增量收集器把堆棧分為多個域,每次僅從一個域收集垃圾。這會造成較小的應用程序中斷。
2.5.分代收集器
這種收集器把堆棧分為兩個或多個域,用以存放不同壽命的對象。jvm生成的新對象一般放在其中的某個域中。過一段時間,繼續存在的對象將獲得使用期並轉入更長壽命的域中。分代收集器對不同的域使用不同的演算法以優化性能。
2.6.並發收集器
並發收集器與應用程序同時運行。這些收集器在某點上(比如壓縮時)一般都不得不停止其他操作以完成特定的任務,但是因為其他應用程序可進行其他的後台操作,所以中斷其他處理的實際時間大大降低。
2.7.並行收集器
並行收集器使用某種傳統的演算法並使用多線程並行的執行它們的工作。在多cpu機器上使用多線程技術可以顯著的提高java應用程序的可擴展性。