1. 程式人生 > 程式設計 >JavaScript實現單鏈表過程解析

JavaScript實現單鏈表過程解析

目錄

什麼是垃圾回收器

垃圾收集器 (GC) 自動管理應用程式的動態記憶體分配請求。

預設選擇

垃圾收集器、堆大小和執行時編譯器預設選擇:

  1. 垃圾優先使用 (G1) 收集器
  2. 最大 GC 執行緒數受堆大小和可用 CPU 資源的限制
  3. 初始堆大小為實體記憶體的 1/64
  4. 最大堆大小為實體記憶體的 1/4
  5. 分層編譯器,同時使用 C1 和 C2

GC的效能指標

  1. HotSpot VM 垃圾收集器可以配置為優先滿足以下兩個目標之一:最小暫停時間和最大吞吐量。如果達到首選目標,收集器將嘗試最大化另一個。
  2. 垃圾收集開銷:吞吐量的補數,垃圾收集所用時間與總執行時間的比例。
  3. 收集頻率:相較於應用程式的執行,收集操作發生的頻率。
  4. 快速:一個物件從誕生到被回收所經歷的時間。
  5. 記憶體佔用:Java堆區佔用的記憶體大小

暫停時間

  1. "暫停時間"指一個時間段內應用程式執行緒暫停,讓GC執行緒執行的狀態
  2. 暫停時間優先意味著儘可能讓單次STW的時間嘴短

吞吐量

  1. 吞吐量:執行使用者程式碼時間/(垃圾收集所花費的時間 + 執行使用者程式碼時間)。
  2. 吞吐量優先意味著在單位時間內STW的時間最短

小結

  1. 一個GC演算法只能針對兩個目標之一
  2. 現在標準:在最大吞吐量優先的情況下,降低停頓時間

GC與垃圾分代的關係

  1. 新生代收集器:Serial ParNew、Parallel Scavenge
  2. 老年代收集器:Serial Old、Parallel Old、CMS
  3. 整堆收集器:G1

Serial回收器(序列回收)

  1. Serial收集器使用單個執行緒來執行所有垃圾收集工作,這使得它相對高效,因為執行緒之間沒有通訊開銷。
  2. Serial 收集器採用複製演算法、序列回收和"Stop-The-World”機制的方式執行記憶體回收。
  3. Serial收集器還提供用於執行老年代垃圾收集的serial old收集器。Serial old收集器同樣也採用了序列回收和"stop the world"機制,只不過記憶體回收演算法使用的是標記-壓縮演算法。
  4. 顯式啟用Serial收集器: -XX:+UseSerialGC。
  5. 現在一般不用Serial收集器

ParNew回收器(並行回收)

  1. Serial收集器和ParNew收集器之間的主要區別在於ParNew收集器具有多個執行緒。
  2. 對於新生代,垃圾次數頻繁,使用並行方式高效
  3. 對於老年代,回收次數少,使用序列方式節省資源
  4. 在單個cPU的環境下,ParNew收集器不比Serial 收集器更高效。雖然Serial收集器是基於序列回收,但是由於CPU不需要頻繁地做任務切換,因此可以有效避免多執行緒互動過程中產生的一些額外開銷。
  5. -XX:+UseParallelGC:使用ParNew收集器、-XX:ParallerGCThreads:限制執行緒數量,預設開啟和CPU資料相同的執行緒數,例如-XX:ParallelGCThreads=4

Parallel 回收器

  1. Parallel收集器也稱為吞吐量收集器
  2. 採用了複製演算法、並行回收和STW機制
  3. Parallel收集器在JDK1.6時提供了用於執行老年代垃圾收集的Parallel old收集器,用來代替老年代的serial old收集器。
  4. Parallel Old收集器採用了標記-壓縮演算法,但同樣也是基於並行回收和"Stop-The-World”機制,是jdk8的預設垃圾收集器
  5. 在程式吞吐量優先的應用場景中,Parallel收集器和Parallel old收集器的組合,在server模式下的記憶體回收效能很不錯。

引數

  1. -XX:+UseParallelGC:指定年輕代用並行收集器
  2. -XX:+UseParallelOldGC:指定老年代使用並行收集器,1和2兩個引數開啟一個,另一個也會被開啟
  3. -XX:ParallelGCThreads:設定年輕代並行收集器的執行緒數,最好和CPU數量相等
  4. -XX:+UseAdaptiveSizePolicy:設定Parallel收集器具有自適應調節策略
  5. -XX:MaxGCPauseMillis:設定垃圾收集器最大停頓時間,單位是毫秒
  6. -XX:GCTimeRatio:垃圾收集時間佔總時間的比例,用於衡量吞吐量的大小。

CMS回收器(低延遲)

  1. CMS 收集器:此收集器適用於喜歡較短的垃圾收集暫停並且能夠與垃圾收集共享處理器資源的應用程式。
  2. CMS採用標記-清除演算法。
  3. 使用-XX:+UseConcMarkSweepGC啟用 CMS 收集器
  4. 從 JDK 9 開始不推薦使用 CMS 收集器
  5. 與其他可用的收集器類似,CMS 收集器是分代的;因此,次要和主要收集都會發生。CMS 收集器嘗試通過使用單獨的垃圾收集器執行緒在應用程式執行緒的執行的同時跟蹤可到達的物件來減少由於主要收集而導致的暫停時間。
  6. 在每個主要的收集週期中,CMS 收集器會在收集開始時暫停所有應用程式執行緒一小段時間,並在收集中間再次暫停。第二次停頓往往是兩次停頓中較長的一次。多個執行緒在兩次暫停期間執行收集工作。一個或多個垃圾收集器執行緒完成剩餘的收集工作(包括大部分活動物件的跟蹤和不可達物件的清掃)。次要收集可以與正在進行的主要迴圈交錯,並以類似於並行收集器的方式完成(特別是,應用程式執行緒在次要收集期間停止)。
  7. 會產生記憶體碎片,為新物件分配記憶體空間只能選擇空閒列表執行記憶體分配

CMS工作原理

  1. 初始標記階段:CMS 收集器在併發收集週期中兩次暫停應用程式。第一個暫停是將可從根直接訪問的物件(例如,來自應用程式執行緒堆疊和暫存器的物件引用、靜態物件等)和堆中的其他地方(例如,年輕代)標記為活動物件
  2. 併發標記階段:從Gc Roots的直接關聯物件開始遍歷整個物件圖的過程,這個過程耗時較長但是不需要停頓使用者執行緒,可以與垃圾收集執行緒一起併發執行。
  3. 重新標記階段:第一次停頓被稱為 initial mark pause. 第二次暫停出現在併發跟蹤階段結束時,它會在 CMS 收集器完成跟蹤該物件後查詢由於應用程式執行緒更新物件中的引用而被併發跟蹤遺漏的物件。這第二次停頓被稱為remark pause.
  4. 此階段清理刪除掉標記階段判斷的已經死亡的物件,釋放記憶體空間。由於不需要移動存活物件,所以這個階段也是可以與使用者執行緒同時併發的

CMS優點

併發收集、低延遲

CMS缺點

  1. 會產生記憶體碎片,導致併發清除後,使用者執行緒可用的空間不足。在無法分配大物件的情況下,不得不提前觸發Full GC。
  2. CMS收集器對CPU資源非常敏感。在併發階段,它雖然不會導致使用者停頓,但是會因為佔用了一部分執行緒而導致應用程式變慢,總吞吐量會降低。
  3. CMS收集器無法處理浮動垃圾

CMS引數

  1. -XX:+UseConcMarkSweepGC:指定使用CMS執行垃圾回收,開啟引數後會自動將-XX:UseParNewGC開啟,即ParNew(Young區) + CMS(Old區) + Serial Old組合
  2. 如果老年代的佔用率超過初始佔用率(老年代的百分比),併發收集也會開始。此初始佔用閾值的預設值約為 92%,但該值可能會因版本而異。可以使用命令列選項手動調整此值-XX:CMSInitiatingOccupancyFraction=,其中是舊代大小的整數百分比(0 到 100。

六種垃圾回收器小結

  1. 如果你想要最小化地使用記憶體和並行開銷,請選Serial GC;
  2. 如果你想要最大化應用程式的吞吐量,請選Parallel GC;
  3. 如果你想要最小化cc的中斷或停頓時間,請選CMS GC。

Garbage-First (G1)

Garbage-First簡介

  1. Garbage-First (G1) 垃圾收集器針對具有大量記憶體的多處理器機器。它試圖以高概率滿足垃圾收集暫停時間目標,同時在幾乎不需要配置的情況下實現高吞吐量。G1 旨在使用當前的目標應用程式和環境在延遲和吞吐量之間提供最佳平衡。
  2. G1 替換了併發標記掃描 (CMS) 收集器。它也是預設收集器。
  3. Garbage-First 垃圾收集器是預設收集器,不必執行任何其他操作。可以通過 -XX:+UseG1GC在命令列上提供來顯式啟用它
  4. G1 是一個分代的、增量的、並行的、主要是併發的、STW和疏散垃圾收集器,它監視每個STW暫停中的暫停時間目標。與其他收集器類似,G1 將堆分成(虛擬)年輕代和年老代。空間回收工作集中在最有效的年輕代上,偶爾在老年代進行空間回收
  5. 為了使空間回收的STW暫停較短,G1 逐步並行地執行空間回收。G1 通過跟蹤有關先前應用程式行為和垃圾收集暫停的資訊來構建相關成本模型,從而實現可預測性。它使用此資訊來確定在暫停中完成的工作的大小。例如,G1 首先回收效率最高的區域(即大部分被垃圾填滿的區域,因此得名)中的空間。
  6. G1收集器不是實時收集器。它試圖在更長的時間內以高概率滿足設定的暫停時間目標,但對於給定的暫停並不總是絕對確定。
  7. G1 將堆劃分為一組大小相等的堆區域,每個區域都是連續的虛擬記憶體範圍,如圖 9-1 所示。區域是記憶體分配和記憶體回收的單位。在任何給定時間,這些區域中的每一個都可以是空的(淺灰色),或者分配給特定的一代,無論是年輕的還是年老的。當記憶體請求到來時,記憶體管理器會分發空閒區域。記憶體管理器將它們分配給一代,然後將它們作為可用空間返回給應用程式,應用程式可以在其中分配自己。
  8. 圖9-1說明:年輕代包含 eden 區域(紅色)和倖存者區域(紅色帶“S”)。這些區域提供與其他收集器中的相應連續空間相同的功能,不同之處在於在 G1 中,這些區域通常以非連續模式佈置在記憶體中。老區(淺藍色)組成老年代。對於跨越多個區域的物件,老年代區域可能是巨大的(帶有“H”的淺藍色)。
  9. 空間整合:G1將記憶體劃分為一個個的region。記憶體的回收是以region作為基本單位的。Region之間是複製演算法,但整體上實際可看作是標記-壓縮(Mark-Compact演算法,兩種演算法都可以避免記憶體碎片。這種特性有利於程式長時間執行,分配大物件時不會因為無法找到連續記憶體空間而提前觸發下一次 GC。尤其是當Java堆非常大的時候,G1的優勢更加明顯。
  10. 不超過幾百毫秒的可預測的暫停時間目標,避免長時間的垃圾收集暫停

垃圾回收週期

  1. 主要過程
    1.1 年輕代Gc(Young GC)
    1.2 老年代併發標記過程(Concurrent Marking)
    1.3 混合回收(Mixed Gc)
  2. 應用程式分配記憶體,當年輕代的Eden區用盡時開始年輕代回收過程;G1的年輕代收集階段是一個並行的獨佔式收集器。在年輕代回收期,G1 Gc暫停所有應用程式執行緒,啟動多執行緒執行年輕代回收。然後從年輕代區間移動存活物件到Survivor區間或者老年區間,也有可能是兩個區間都會涉及。
  3. 當堆記憶體使用達到一定值(預設45%)時,開始老年代併發標記過程。
  4. 標記完成馬上開始混合回收過程。對於一個混合回收期,G1 Gc從老年區間移動存活物件到空閒區間,這些空閒區間也就成為了老年代的一部分。和年輕代不同,老年代的G1回收器和其他cc不同,G1的老年代回收器不需要整個老年代被回收,一次只需要掃描/回收一小部分老年代的Region就可以了。同時,這個老年代Region是和年輕代一起被回收的。

Remembered Set

  1. 無論G1還是其他分代收集器,JVM都是使用Remembered Set來避免全域性掃描:
  2. 每個Region都有一個對應的Remembered Set;
  3. 每次Reference型別資料寫操作時,都會產生一個Write Barrier暫時中斷操作;
  4. 然後檢查將要寫入的引用指向的物件是否和該Reference型別資料在不同的Region(其他收集器:檢查老年代物件是否引用了新生代物件)﹔
  5. 如果不同,通過cardTable把相關引用資訊記錄到引用指向物件的所在Region對應的Remembered set中;
  6. 當進行垃圾收集時,在Gc根節點的列舉範圍加入Remembered Set;就可以保證不進行全域性掃描,也不會有遺漏。
  7. JVM啟動時,G1先準備好Eden區,程式在執行過程中不斷建立物件到Eden區,當Eden空間耗盡時,G1會啟動一次年輕代垃圾回收過程。
  8. 年輕代垃圾回收只會回收Eden區和survivor區。
  9. YGC時,首先G1停止應用程式的執行(Stop-The-World),G1建立回收集(Collection Set),回收集是指需要被回收的記憶體分段的集合,年輕代回收過程的回收集包含年輕代Eden區和Survivor區所有的記憶體分段。

G1回收過程

1. 年輕代GC

  1. 掃描根
    根是指static變數指向的物件,正在執行的方法呼叫鏈條上的區域性變數等。根引用連同RSet記錄的外部引用作為掃描存活物件的入口。
  2. 更新RSet
    處理dirty card queue中的card,更新RSet。此階段完成後,RSet可以準確的反映老年代對所在的記憶體分段中物件的引用。
    card:對於應用程式的引用賦值語句object.field=object , JVM會在之前和之後執行特殊的操作以在dirty card queue中入隊一個儲存了物件引用資訊的card。在年輕代回收的時候,G1會對Dirty Card Queue中所有的card進行處理,以更新RSet,保證RSet實時準確的反映引用關係。
  3. 處理RSet
    識別被老年代物件指向的Eden中的物件,這些被指向的Eden中的物件被認為是存活的物件。
  4. 複製物件
    此階段,物件樹被遍歷,Eden區記憶體段中存活的物件會被複制到survivor區中空的記憶體分段Survivor區記憶體段中存活的物件如果年齡未達閾值,年齡會加1,達到閥值會被會被複制到old區中空的記憶體分段。如果Survivor空間不夠,Eden空間的部分資料會直接晉升到老年代空間
  5. 處理引用
    處理Soft,weak,Phantom,Final,3NI weak 等引用。最終Eden空間的資料為空,Gc停止工作,而目標記憶體中的物件都是連續儲存的,沒有碎片,所以複製過程可以達到記憶體整理的效果,減少碎片。

2. 併發標記過程

1.初始標記階段:標記從根節點直接可達的物件。這個階段是STW的,並且會觸發一次年輕代GC。
2.根區域掃描(Root Region Scanning) : 61 Gc掃描survivor區直接可達的老年代區域物件,並標記被引用的物件。這一過程必須在young Gc之前完成。
3. 併發標記(Concurrent Marking):在整個堆中進行併發標記(和應用程式併發執行),此過程可能被young Gc中斷。在併發標記階段,若發現區域物件中的所有物件都是垃圾,那這個區域會被立即回收。同時,併發標記過程中,會計算每個區域的物件活性(區域中存活物件的比例)。
4、再次標記(Remark):由於應用程式持續進行,需要修正上一次的標記結果。是STw的。G1中採用了比CMS更快的初始快照演算法:snapshot-at-the-beginning (SATB)。
5.獨佔清理(cleanup,STw):計算各個區域的存活物件和6c回收比例,並進行排序,識別可以混合回收的區域。為下階段做鋪墊。是STW的。這個階段並不會實際上去做垃圾的收集
6.併發清理階段:識別並清理完全空閒的區域。

3. 混合回收

  1. 當越來越多的物件晉升到老年代oldregion時,為了避免堆記憶體被耗盡,虛擬機器會觸發一個混合的垃圾收集器,即Mixed Gc,該演算法並不是一個oldGc,除了回收整個Young Region,還會回收一部分的o1d Region。
  2. 併發標記結束以後,老年代中百分百為垃圾的記憶體分段被回收了,部分為垃圾的記憶體分段被計算了出來。預設情況下,這些老年代的記憶體分段會分8次(可以通過-XX:G1MixedGccountTarget設定)被回收。
  3. 混合回收的回收集(collection Set)包括八分之一的老年代記憶體分段,Eden區記憶體分段,Survivor區記憶體分段。混合回收的演算法和年輕代回收的演算法完全一樣,只是回收集多了老年代的記憶體分段。
  4. 由於老年代中的記憶體分段預設分8次回收,G1會優先回收垃圾多的記憶體分段。垃圾佔記憶體分段比例越高的,越會被先回收。並且有一個閾值會決定記憶體分段是否被回收,-XX:G1MixedGcLiveThresholdPercent,預設為65%,意思是垃圾佔記憶體分段比例要達到65%才會被回收。如果垃圾佔比太低,意味著存活的物件佔比高,在複製的時候會花費更多的時間。
  5. 混合回收並不一定要進行8次。有一個閾值-XX:G1HeapwastePercent,預設值為10%,意思是允許整個堆記憶體中有1e%的空間被浪費,意味著如果發現可以回收的垃圾佔堆記憶體的比例低於10%,則不再進行混合回收。因為Gc會花費很多的時間但是回收到的記憶體卻很少。

ZGC