您好,登錄后才能下訂單哦!
這篇文章將為大家詳細講解有關怎么理解Java常見知識點中的垃圾回收機制,文章內容質量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關知識有一定的了解。
Java堆中存放著大量的Java對象實例,在垃圾收集器回收內存前,第一件事情就是確定哪些對象是“活著的”,哪些是可以回收的。
Java中Stop-The-World機制簡稱STW,是在執行垃圾收集算法時,Java應用程序的其他所有線程都被掛起(除了垃圾收集幫助器之外)。Java中一種全局暫停現象,全局停頓,所有Java代碼停止,native代碼可以執行,但不能與JVM交互;這些現象多半是由于gc引起。
1. 引用計數算法(基本棄用)
引用計數算法是判斷對象是否存活的基本算法:給每個對象添加一個引用計數器,沒當一個地方引用它的時候,計數器值加1;當引用失效后,計數器值減1。但是這種方法有一個致命的缺陷,當兩個對象相互引用時會導致這兩個都無法被回收。
2. 根搜索算法(目前使用中)
在主流的商用語言中(Java、C#…)都是使用根搜索算法來判斷對象是否存活。對于程序來說,根對象總是可以訪問的。從這些根對象開始,任何可以被觸及的對象都被認為是”活著的”的對象。無法觸及的對象被認為是垃圾,需要被回收。
Java虛擬機的根對象集合根據實現不同而不同,但是總會包含以下幾個方面:
棧(棧幀中的本地變量表)中引用的對象。
方法區中的類靜態屬性引用的變量。
方法區中的常量引用的變量。
本地方法JNI的引用對象。
區分活動對象和垃圾的兩個基本方法是引用計數和根搜索。 引用計數是通過為堆中每個對象保存一個計數來區分活動對象和垃圾。根搜索算法實際上是追蹤從根結點開始的引用圖。
在主流的商用程序語言(如我們的Java)的主流實現中,都是通過可達性分析算法來判定對象是否存活的。
1. 標記-清除算法
分標記和清除兩個階段:首先標記處所需要回收的對象,在標記完成后統一回收所有被標記的對象。
它有兩點不足:一個效率問題,標記和清除過程都效率不高;一個是空間問題,標記清除之后會產生大量不連續的內存碎片(類似于我們電腦的磁盤碎片),空間碎片太多導致需要分配大對象時無法找到足夠的連續內存而不得不提前觸發另一次垃圾回收動作。
2. 復制算法
為了解決效率問題,出現了“復制”算法,他將可用內存按容量劃分為大小相等的兩塊,每次只需要使用其中一塊。當一塊內存用完了,將還存活的對象復制到另一塊上面,然后再把剛剛用完的內存空間一次清理掉。這樣就解決了內存碎片問題,但是代價就是可以用內容就縮小為原來的一半。
3. 標記-整理算法
復制算法在對象存活率較高時就會進行頻繁的復制操作,效率將降低。因此又有了標記-整理算法,標記過程同標記-清除算法,但是在后續步驟不是直接對對象進行清理,而是讓所有存活的對象都向一側移動,然后直接清理掉端邊界以外的內存。
4. 分代收集法
當前商業虛擬機的GC都是采用分代收集算法
為了增大垃圾收集的效率,所以JVM將堆進行分代,分為不同的部分,一般有三部分,新生代,老年代和永久代(在新的版本中已經將永久代廢棄,引入了元空間的概念,永久代使用的是JVM內存而元空間直接使用物理內存):
新生代(對應minor GC):所有新new出來的對象都會最先出現在新生代中,當新生代這部分內存滿了之后,就會發起一次垃圾收集事件,這種發生在新生代的垃圾收集稱為Minor collections。這種收集通常比較快,因為新生代的大部分對象都是需要回收的,那些暫時無法回收的就會被移動到老年代。
老年代(對應Full GC):老年代用來存儲那些存活時間較長的對象。一般來說,我們會給新生代的對象限定一個存活的時間,當達到這個時間還沒有被收集的時候就會被移動到老年代中。
永久代:用于存放靜態文件,如Java類、方法等。持久代對垃圾回收沒有顯著影響,但是有些應用可能動態生成或者調用一些class,例如Hibernate 等,在這種時候需要設置一個比較大的持久代空間來存放這些運行過程中新增的類。
程序中主動調用System.gc()強制執行的GC為Full GC。
大概流程:
內存分區:
年輕代(Young Generation)(Eden,Survivor-s0,Survivor-s1,大小比例默認為8:1:1)
年老代(Old Generation)
永久代(Permanent Generation)。(包含應用的類/方法信息, 以及JRE庫的類和方法信息.和垃圾回收基本無關)
新生代中的對象“朝生夕死”,每次GC時都會有大量對象死去,少量存活,使用復制算法。新生代又分為Eden區和Survivor區(Survivor from、Survivor to),大小比例默認為8:1:1。
老年代中的對象因為對象存活率高、沒有額外空間進行分配擔保,就使用標記-清除或標記-整理算法。
新產生的對象優先進去Eden區,當Eden區滿了之后再使用Survivor from,當Survivor from 也滿了之后就進行Minor GC(新生代GC),將Eden和Survivor from中存活的對象copy進入Survivor to,然后清空Eden和Survivor from,這個時候原來的Survivor from成了新的Survivor to,原來的Survivor to成了新的Survivor from。復制的時候,如果Survivor to 無法容納全部存活的對象,則根據老年代的分配擔保(類似于銀行的貸款擔保)將對象copy進去老年代,如果老年代也無法容納,則進行Full GC(老年代GC)。
大對象直接進入老年代:JVM中有個參數配置-XX:PretenureSizeThreshold,令大于這個設置值的對象直接進入老年代,目的是為了避免在Eden和Survivor區之間發生大量的內存復制。
長期存活的對象進入老年代:JVM給每個對象定義一個對象年齡計數器,如果對象在Eden出生并經過第一次Minor GC后仍然存活,并且能被Survivor容納,將被移入Survivor并且年齡設定為1。每熬過一次Minor GC,年齡就加1,當他的年齡到一定程度(默認為15歲,可以通過XX:MaxTenuringThreshold來設定),就會移入老年代。
但是JVM并不是永遠要求年齡必須達到最大年齡才會晉升老年代,如果Survivor 空間中相同年齡(如年齡為x)所有對象大小的總和大于Survivor的一半,年齡大于等于x的所有對象直接進入老年代,無需等到最大年齡要求。
垃圾回收算法是方法論,垃圾回收器是實現。
Serial收集器:串行收集器是最古老,最穩定以及效率高的收集器,可能會產生較長的停頓,只使用一個線程去回收。(STW)它是虛擬機運行在client模式下的默認新生代收集器:簡單而高效(與其他收集器的單個線程相比,因為沒有線程切換的開銷等)。
ParNew收集器:ParNew收集器其實就是Serial收集器的多線程版本。(STW)是許多運行在Server模式下的JVM中首選的新生代收集器,其中一個很重還要的原因就是除了Serial之外,只有他能和老年代的CMS收集器配合工作。
Parallel Scavenge收集器:Parallel Scavenge收集器類似ParNew收集器,Parallel收集器更關注系統的吞吐量(就是CPU運行用戶代碼的時間與CPU總消耗時間的比值,即 吞吐量=運行用戶代碼的時間/[運行用戶代碼的時間+垃圾收集時間])。
CMS收集器:CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間為目標的收集器。,停頓時間短,用戶體驗就好。
基于“標記清除”算法,并發收集、低停頓,運作過程復雜,分4步:
初始標記:僅僅標記GC Roots能直接關聯到的對象,速度快,但是需要“Stop The World”
并發標記:就是進行追蹤引用鏈的過程,可以和用戶線程并發執行。
重新標記:修正并發標記階段因用戶線程繼續運行而導致標記發生變化的那部分對象的標記記錄,比初始標記時間長但遠比并發標記時間短,需要“Stop The World”
并發清除:清除標記為可以回收對象,可以和用戶線程并發執行
由于整個過程耗時最長的并發標記和并發清除都可以和用戶線程一起工作,所以總體上來看,CMS收集器的內存回收過程和用戶線程是并發執行的。
CSM收集器有3個缺點:
1)對CPU資源非常敏感
并發收集雖然不會暫停用戶線程,但因為占用一部分CPU資源,還是會導致應用程序變慢,總吞吐量降低。
CMS的默認收集線程數量是=(CPU數量+3)/4;當CPU數量多于4個,收集線程占用的CPU資源多于25%,對用戶程序影響可能較大;不足4個時,影響更大,可能無法接受。
2)無法處理浮動垃圾(在并發清除時,用戶線程新產生的垃圾叫浮動垃圾),可能出現”Concurrent Mode Failure”失敗。
并發清除時需要預留一定的內存空間,不能像其他收集器在老年代幾乎填滿再進行收集;如果CMS預留內存空間無法滿足程序需要,就會出現一次”Concurrent Mode Failure”失敗;這時JVM啟用后備預案:臨時啟用Serail Old收集器,而導致另一次Full GC的產生;
3)產生大量內存碎片:CMS基于”標記-清除”算法,清除后不進行壓縮操作產生大量不連續的內存碎片,這樣會導致分配大內存對象時,無法找到足夠的連續內存,從而需要提前觸發另一次Full GC動作。
Serial Old收集器:Serial 收集器的老年代版本,單線程,“標記整理”算法,主要是給Client模式下的虛擬機使用。可以作為CMS的后背方案,在CMS發生Concurrent Mode Failure是使用
Parallel Old 收集器:Parallel Scavenge的老年代版本,多線程,“標記整理”算法,JDK 1.6才出現。在此之前Parallel Scavenge只能同Serial Old搭配使用,由于Serial Old的性能較差導致Parallel Scavenge的優勢發揮不出來,Parallel Old收集器的出現,使“吞吐量優先”收集器終于有了名副其實的組合。在吞吐量和CPU敏感的場合,都可以使用Parallel Scavenge/Parallel Old組合。
G1收集器:G1 (Garbage-First)是一款面向服務器的垃圾收集器,主要針對配備多顆處理器及大容量內存的機器. 以極高概率滿足GC停頓時間要求的同時,還具備高吞吐量性能特征。G1是面向服務端應用的垃圾收集器。它的使命是未來可以替換掉CMS收集器。
關于怎么理解Java常見知識點中的垃圾回收機制就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。