導航:首頁 > 源碼編譯 > 引用計數演算法

引用計數演算法

發布時間:2024-09-27 07:09:47

1. 各種編程語言的實現都採用了哪些垃圾回收演算法

從各種垃圾收集演算法最基本的運行方式來說,大概可以分成三個類型:

1. 引用計數(reference counting):

基本思路是為每個對象加一個計數器,記錄指向這個對象的引用數量。每次有一個新的引用指向這個對象,計數器加一;反之每次有一個指向這個對象引用被置空或者指向其他對象,計數器減一。當計數器變為 0 的時候,自動刪除這個對象。

引用計數的優點是 1)相對簡單,不需要太多運行時(run-time)的支持,可以在原生不支持 GC 的語言里實現。2)對象會在成為垃圾的瞬間被釋放,不會給正常程序的執行帶來額外中斷。它的死穴是循環引用,對象 A 包含一個引用指向對象 B ,同時對象 B 包含一個引用指向對象 A,計數器就抓瞎了。另外,引用計數對正常程序的執行性能有影響(每次引用賦值都要改計數器),特別是在多線程環境下(改計數器要加鎖同步)。

現在仍然主要採用引用計數的例子有 Apple 的 ARC,C++ 新標准里的 std::shared_ptr。

2. 標記-清掃(mark-sweep)。

基本思路是先按需分配,等到沒有空閑內存的時候從寄存器和程序棧上的引用出發,遍歷以對象為節點、以引用為邊構成的圖,把所有可以訪問到的對象打上標記,然後清掃一遍內存空間,把所有沒標記的對象釋放。

標記-清掃沒有無法處理循環引用的問題,不觸發 GC 時也不影響正常程序的執行性能。但它的問題是當內存耗盡觸發 GC 時,需要中斷正常程序一段時間來清掃內存,在內存大對象多的時候這個中斷可能很長。

採用或者部分採用標記-清掃的例子非常多,不一一列舉了。

3. 節點復制(ing)。

基本思路是把整個內存空間一分為二,不妨記為 A 和 B。所有對象的內存在 A 中分配,當 A 塞滿的時候,同樣從寄存器和程序棧上的引用出發,遍歷以對象為節點、以引用為邊構成的圖,把所有可以訪問到的對象復制到 B 去,然後對調 A 和 B 的角色。

相對於標記-清掃,節點復制的主要缺點是總有一半空間空閑著無法利用,另一個比較隱晦的缺點是它使用內存的方式與現有的內存換頁、Cache 換入換出機制有潛在的沖突。但它有個很大的優點: 所有的對象在內存中永遠都是緊密排列的,所以分配內存的任務變得極為簡單,只要移動一個指針即可。對於內存分配頻繁的環境來說,性能優勢相當大。另外,由於不需要清掃整個內存空間,所以如果內存中存活對象很少而垃圾對象很多的話(有些語言有這個傾向),觸發 GC 造成的中斷會小於標記-清掃。

同樣的,採用或者部分採用節點復制的例子也非常多,不一一列舉了。

2. java垃圾回收怎麼理解

Java的堆是一個運行時數據區,類的實例(對象)從中分配空間。Java虛擬機(JVM)的堆中儲存著正在運行的應用程序所建立的所有對象,這些對象通過new、newarray、anewarray和multianewarray等指令建立,但是它們不需要程序代碼來顯式地釋放。一般來說,堆的是由垃圾回收來負責的,盡管JVM規范並不要求特殊的垃圾回收技術,甚至根本就不需要垃圾回收,但是由於內存的有限性,JVM在實現的時候都有一個由垃圾回收所管理的堆。垃圾回收是一種動態存儲管理技術,它自動地釋放不再被程序引用的對象,按照特定的垃圾收集演算法來實現資源自動回收的功能。

垃圾收集的意義

在C++中,對象所佔的內存在程序結束運行之前一直被佔用,在明確釋放之前不能分配給其它對象;而在Java中,當沒有對象引用指向原先分配給某個對象的內存時,該內存便成為垃圾。JVM的一個系統級線程會自動釋放該內存塊。垃圾收集意味著程序不再需要的對象是"無用信息",這些信息將被丟棄。當一個對象不再被引用的時候,內存回收它佔領的空間,以便空間被後來的新對象使用。事實上,除了釋放沒用的對象,垃圾收集也可以清除內存記錄碎片。由於創建對象和垃圾收集器釋放丟棄對象所佔的內存空間,內存會出現碎片。碎片是分配給對象的內存塊之間的空閑內存洞。碎片整理將所佔用的堆內存移到堆的一端,JVM將整理出的內存分配給新的對象。

垃圾收集能自動釋放內存空間,減輕編程的負擔。這使Java 虛擬機具有一些優點。首先,它能使編程效率提高。在沒有垃圾收集機制的時候,可能要花許多時間來解決一個難懂的存儲器問題。在用Java語言編程的時候,靠垃圾收集機制可大大縮短時間。其次是它保護程序的完整性, 垃圾收集是Java語言安全性策略的一個重要部份。

垃圾收集的一個潛在的缺點是它的開銷影響程序性能。Java虛擬機必須追蹤運行程序中有用的對象, 而且最終釋放沒用的對象。這一個過程需要花費處理器的時間。其次垃圾收集演算法的不完備性,早先採用的某些垃圾收集演算法就不能保證100%收集到所有的廢棄內存。當然隨著垃圾收集演算法的不斷改進以及軟硬體運行效率的不斷提升,這些問題都可以迎刃而解。

垃圾收集的演算法分析

Java語言規范沒有明確地說明JVM使用哪種垃圾回收演算法,但是任何一種垃圾收集演算法一般要做2件基本的事情:(1)發現無用信息對象;(2)回收被無用對象佔用的內存空間,使該空間可被程序再次使用。

大多數垃圾回收演算法使用了根集(root set)這個概念;所謂根集就量正在執行的Java程序可以訪問的引用變數的集合(包括局部變數、參數、類變數),程序可以使用引用變數訪問對象的屬性和調用對象的方法。垃圾收集首選需要確定從根開始哪些是可達的和哪些是不可達的,從根集可達的對象都是活動對象,它們不能作為垃圾被回收,這也包括從根集間接可達的對象。而根集通過任意路徑不可達的對象符合垃圾收集的條件,應該被回收。下面介紹幾個常用的演算法。

1、 引用計數法(Reference Counting Collector)

引用計數法是唯一沒有使用根集的垃圾回收的法,該演算法使用引用計數器來區分存活對象和不再使用的對象。一般來說,堆中的每個對象對應一個引用計數器。當每一次創建一個對象並賦給一個變數時,引用計數器置為1。當對象被賦給任意變數時,引用計數器每次加1當對象出了作用域後(該對象丟棄不再使用),引用計數器減1,一旦引用計數器為0,對象就滿足了垃圾收集的條件。

基於引用計數器的垃圾收集器運行較快,不會長時間中斷程序執行,適宜地必須 實時運行的程序。但引用計數器增加了程序執行的開銷,因為每次對象賦給新的變數,計數器加1,而每次現有對象出了作用域生,計數器減1。

2、tracing演算法(Tracing Collector)

tracing演算法是為了解決引用計數法的問題而提出,它使用了根集的概念。基於tracing演算法的垃圾收集器從根集開始掃描,識別出哪些對象可達,哪些對象不可達,並用某種方式標記可達對象,例如對每個可達對象設置一個或多個位。在掃描識別過程中,基於tracing演算法的垃圾收集也稱為標記和清除(mark-and-sweep)垃圾收集器.

3、compacting演算法(Compacting Collector)

為了解決堆碎片問題,基於tracing的垃圾回收吸收了Compacting演算法的思想,在清除的過程中,演算法將所有的對象移到堆的一端,堆的另一端就變成了一個相鄰的空閑內存區,收集器會對它移動的所有對象的所有引用進行更新,使得這些引用在新的位置能識別原來 的對象。在基於Compacting演算法的收集器的實現中,一般增加句柄和句柄表。

4、ing演算法(Coping Collector)

該演算法的提出是為了克服句柄的開銷和解決堆碎片的垃圾回收。它開始時把堆分成 一個對象 面和多個空閑面, 程序從對象面為對象分配空間,當對象滿了,基於coping演算法的垃圾 收集就從根集中掃描活動對象,並將每個 活動對象復制到空閑面(使得活動對象所佔的內存之間沒有空閑洞),這樣空閑面變成了對象面,原來的對象面變成了空閑面,程序會在新的對象面中分配內存。

一種典型的基於coping演算法的垃圾回收是stop-and-演算法,它將堆分成對象面和空閑區域面,在對象面與空閑區域面的切換過程中,程序暫停執行。

5、generation演算法(Generational Collector)

stop-and-垃圾收集器的一個缺陷是收集器必須復制所有的活動對象,這增加了程序等待時間,這是coping演算法低效的原因。在程序設計中有這樣的規律:多數對象存在的時間比較短,少數的存在時間比較長。因此,generation演算法將堆分成兩個或多個,每個子堆作為對象的一代(generation)。由於多數對象存在的時間比較短,隨著程序丟棄不使用的對象,垃圾收集器將從最年輕的子堆中收集這些對象。在分代式的垃圾收集器運行後,上次運行存活下來的對象移到下一最高代的子堆中,由於老一代的子堆不會經常被回收,因而節省了時間。

6、adaptive演算法(Adaptive Collector)

在特定的情況下,一些垃圾收集演算法會優於其它演算法。基於Adaptive演算法的垃圾收集器就是監控當前堆的使用情況,並將選擇適當演算法的垃圾收集器。

透視Java垃圾回收

1、命令行參數透視垃圾收集器的運行

2、使用System.gc()可以不管JVM使用的是哪一種垃圾回收的演算法,都可以請求Java的垃圾回收。

在命令行中有一個參數-verbosegc可以查看Java使用的堆內存的情況,它的格式如下:
Java代碼 java -verbosegc classfile java -verbosegc classfile

可以看個例子:
Java代碼 class TestGC { public static void main(String[] args) </SPAN></li> { new TestGC(); </SPAN></li> System.gc(); System.runFinalization(); } } class TestGC { public static void main(String[] args) { new TestGC(); System.gc(); System.runFinalization(); } }

在這個例子中,一個新的對象被創建,由於它沒有使用,所以該對象迅速地變為可達,程序編譯後,執行命令: java -verbosegc TestGC 後結果為:
Java代碼 [Full GC 168K->97K(1984K), 0.0253873 secs] [Full GC 168K->97K(1984K), 0.0253873 secs]
機器的環境為,Windows 2000 + JDK1.3.1,箭頭前後的數據168K和97K分別表示垃圾收集GC前後所有存活對象使用的內存容量,說明有168K-97K=71K的對象容量被回收,括弧內的數據1984K為堆內存的總容量,收集所需要的時間是0.0253873秒(這個時間在每次執行的時候會有所不同)。

2、finalize方法透視垃圾收集器的運行

在JVM垃圾收集器收集一個對象之前 ,一般要求程序調用適當的方法釋放資源,但在沒有明確釋放資源的情況下,Java提供了預設機制來終止化該對象心釋放資源,這個方法就是finalize()。它的原型為:

Java代碼 protected void finalize() throws Throwable protected void finalize() throws Throwable
在finalize()方法返回之後,對象消失,垃圾收集開始執行。原型中的throws Throwable表示它可以拋出任何類型的異常。

之所以要使用finalize(),是由於有時需要採取與Java的普通方法不同的一種方法,通過分配內存來做一些具有C風格的事情。這主要可以通過"固有方法"來進行,它是從Java里調用非Java方法的一種方式。C和C++是目前唯一獲得固有方法支持的語言。但由於它們能調用通過其他語言編寫的子程序,所以能夠有效地調用任何東西。在非Java代碼內部,也許能調用C的malloc()系列函數,用它分配存儲空間。而且除非調用了free(),否則存儲空間不會得到釋放,從而造成內存"漏洞"的出現。當然,free()是一個C和C++函數,所以我們需要在finalize()內部的一個固有方法中調用它。也就是說我們不能過多地使用finalize(),它並不是進行普通清除工作的理想場所。

在普通的清除工作中,為清除一個對象,那個對象的用戶必須在希望進行清除的地點調用一個清除方法。這與C++"破壞器"的概念稍有抵觸。在C++中,所有對象都會破壞(清除)。或者換句話說,所有對象都"應該"破壞。若將C++對象創建成一個本地對象,比如在堆棧中創建(在Java中是不可能的),那麼清除或破壞工作就會在"結束花括弧"所代表的、創建這個對象的作用域的末尾進行。若對象是用new創建的(類似於Java),那麼當程序員調用C++的delete命令時(Java沒有這個命令),就會調用相應的破壞器。若程序員忘記了,那麼永遠不會調用破壞器,我們最終得到的將是一個內存"漏洞",另外還包括對象的其他部分永遠不會得到清除。

相反,Java不允許我們創建本地(局部)對象--無論如何都要使用new。但在Java中,沒有"delete"命令來釋放對象,因為垃圾收集器會幫助我們自動釋放存儲空間。所以如果站在比較簡化的立場,我們可以說正是由於存在垃圾收集機制,所以Java沒有破壞器。然而,隨著以後學習的深入,就會知道垃圾收集器的存在並不能完全消除對破壞器的需要,或者說不能消除對破壞器代表的那種機制的需要(而且絕對不能直接調用finalize(),所以應盡量避免用它)。若希望執行除釋放存儲空間之外的其他某種形式的清除工作,仍然必須調用Java中的一個方法。它等價於C++的破壞器,只是沒後者方便。

關於垃圾收集的幾點補充

經過上述的說明,可以發現垃圾回收有以下的幾個特點:

(1)垃圾收集發生的不可預知性:由於實現了不同的垃圾收集演算法和採用了不同的收集機制,所以它有可能是定時發生,有可能是當出現系統空閑CPU資源時發生,也有可能是和原始的垃圾收集一樣,等到內存消耗出現極限時發生,這與垃圾收集器的選擇和具體的設置都有關系。

(2)垃圾收集的精確性:主要包括2 個方面:(a)垃圾收集器能夠精確標記活著的對象;(b)垃圾收集器能夠精確地定位對象之間的引用關系。前者是完全地回收所有廢棄對象的前提,否則就可能造成內存泄漏。而後者則是實現歸並和復制等演算法的必要條件。所有不可達對象都能夠可靠地得到回收,所有對象都能夠重新分配,允許對象的復制和對象內存的縮並,這樣就有效地防止內存的支離破碎。

(3)現在有許多種不同的垃圾收集器,每種有其演算法且其表現各異,既有當垃圾收集開始時就停止應用程序的運行,又有當垃圾收集開始時也允許應用程序的線程運行,還有在同一時間垃圾收集多線程運行。

(4)垃圾收集的實現和具體的JVM 以及JVM的內存模型有非常緊密的關系。不同的JVM 可能採用不同的垃圾收集,而JVM 的內存模型決定著該JVM可以採用哪些類型垃圾收集。現在,HotSpot 系列JVM中的內存系統都採用先進的面向對象的框架設計,這使得該系列JVM都可以採用最先進的垃圾收集。

(5)隨著技術的發展,現代垃圾收集技術提供許多可選的垃圾收集器,而且在配置每種收集器的時候又可以設置不同的參數,這就使得根據不同的應用環境獲得最優的應用性能成為可能。

針對以上特點,我們在使用的時候要注意:

(1)不要試圖去假定垃圾收集發生的時間,這一切都是未知的。比如,方法中的一個臨時對象在方法調用完畢後就變成了無用對象,這個時候它的內存就可以被釋放。

(2)Java中提供了一些和垃圾收集打交道的類,而且提供了一種強行執行垃圾收集的方法--調用System.gc(),但這同樣是個不確定的方法。Java 中並不保證每次調用該方法就一定能夠啟動垃圾收集,它只不過會向JVM發出這樣一個申請,到底是否真正執行垃圾收集,一切都是個未知數。

(3)挑選適合自己的垃圾收集器。一般來說,如果系統沒有特殊和苛刻的性能要求,可以採用JVM的預設選項。否則可以考慮使用有針對性的垃圾收集器,比如增量收集器就比較適合實時性要求較高的系統之中。系統具有較高的配置,有比較多的閑置資源,可以考慮使用並行標記/清除收集器。

(4)關鍵的也是難把握的問題是內存泄漏。良好的編程習慣和嚴謹的編程態度永遠是最重要的,不要讓自己的一個小錯誤導致內存出現大漏洞。

(5)盡早釋放無用對象的引用。大多數程序員在使用臨時變數的時候,都是讓引用變數在退出活動域(scope)後,自動設置為null,暗示垃圾收集器來收集該對象,還必須注意該引用的對象是否被監聽,如果有,則要去掉監聽器,然後再賦空值。

結束語

一般來說,Java開發人員可以不重視JVM中堆內存的分配和垃圾處理收集,但是,充分理解Java的這一特性可以讓我們更有效地利用資源。同時要注意finalize()方法是Java的預設機制,有時為確保對象資源的明確釋放,可以編寫自己的finalize方法

3. JVM如何判斷哪些對象可以被回收

jvm要做垃圾回收時,首先要判斷一個對象是否還有可能被使用。那麼如何判斷一個對象是否還有可能被用到?
如果我們的程序無法再引用到該對象,那麼這個對象就肯定可以被回收,這個狀態稱為不可達。當對象不可達,該對象就可以作為回收對象被垃圾回收器回收。
那麼這個可達還是不可達如何判斷呢?
答案就是GC roots ,也就是根對象,如果從一個對象沒有到達根對象的路徑,或者說從根對象開始無法引用到該對象,該對象就是不可達的。
以下三類對象在jvm中作為GC roots,來判斷一個對象是否可以被回收
(通常來說我們只要知道虛擬機棧和靜態引用就夠了)
虛擬機棧(JVM stack)中引用的對象(准確的說是虛擬機棧中的棧幀(frames))
我們知道,每個方法執行的時候,jvm都會創建一個相應的棧幀(棧幀中包括操作數棧、局部變數表、運行時常量池的引用),棧幀中包含這在方法內部使用的所有對象的引用(當然還有其他的基本類型數據),當方法執行完後,該棧幀會從虛擬機棧中彈出,這樣一來,臨時創建的對象的引用也就不存在了,或者說沒有任何gc roots指向這些臨時對象,這些對象在下一次GC時便會被回收掉
方法區中類靜態屬性引用的對象
靜態屬性是該類型(class)的屬性,不單獨屬於任何實例,因此該屬性自然會作為gc roots。只要這個class存在,該引用指向的對象也會一直存在。class 也是會被回收的,在面後說明
本地方法棧(Native Stack)引用的對象
一個class要被回收准確的說應該是卸載,必須同時滿足以下三個條件
堆中不存在該類的任何實例
載入該類的classloader已經被回收
該類的java.lang.Class對象沒有在任何地方被引用,也就是說無法通過反射再帶訪問該類的信息
這篇內容太少了,在說幾句java中的四種引用類型
其實這四類引用的區別就在於GC時是否回收該對象
強引用(Strong) 就是我們平時使用的方式 A a = new A();強引用的對象是不會被回收的
軟引用(Soft) 在jvm要內存溢出(OOM)時,會回收軟引用的對象,釋放更多內存
弱引用(Weak) 在下次GC時,弱引用的對象是一定會被回收的
虛引用(Phantom) 對對象的存在時間沒有任何影響,也無法引用對象實力,唯一的作用就是在該對象被回收時收到一個系統通知

4. 關於GC必須要掌握的基礎

如何判斷一個對象是垃圾引用計數法

原理:當一個對象被創建(一般在堆區)時,同時創建一個引用計數器,當這個對象被引用了,則計數器加1,當引用失效之後,則計數器減1。當計數器為0時,則表示此對象為垃圾

問題:存在循環引用問題。當A引用B,B引用C,C又引用A的話,那麼此3個對象永遠不會被判斷為垃圾

可達性演算法

確定某些對象為根對象(GC Roots),從這些根對象出發,遍歷找到和這些根對象有引用關系的對象,形成引用鏈,而不在這些引用鏈上的對象就被判定為垃圾。

如下圖所示,兩個淺藍色的對象即是垃圾。

問題來了,哪些對象可以作為GC Roots?

JVM stack:在虛擬機棧中的對象

native method stack:本地方法棧中的對象

runtime constant pool:運行時常量池中的對象

static reference in method area:在方法區的靜態變數

如何回收垃圾標記清除演算法

根據可達性演算法,將不可達對象標記為垃圾

統一清除所有垃圾

優點:當需要回收的對象較少時,效率較高

缺點:產生大量內存碎片

標記復制演算法

將內存區域分為兩部分,對象只分配在其中一半區域

根據可達性演算法,將不可達對象標記為垃圾

將所有存活對象復制到按序復制到另一半區域,當前區域全部清除

優點:解決出現大量內存碎片問題

缺點:一半的內存區域不可用,浪費內存

標記整理演算法

根據可達性演算法,將不可達對象標記為垃圾

將所有存活對象整理復制到內存的一端

優點:無內存碎片問題,不造成內存浪費

缺點:在復制過程中效率較低

JVM GC分代演算法

眾所周知,JVM堆內存是分代管理的,如下。GC在針對不同區域則會採用不同的GC演算法

年輕代:存放大量朝生夕死的對象,採用標記復制演算法,將eden區和survivor from區的垃圾進行標記,然後將存活對象全部復制到survivor to區。

老年代:存放生命周期長的對象,採用標記整理演算法,每次GC將存活對象復制到內存的一端。

GC的時機

簡而言之:就是當內存不夠時,就會發生GC

Minor GC

時機:Eden無法存放對象時

演算法:標記復制演算法

Major GC(Full GC)

時機:old區無法存放對象時

演算法:標記整理演算法

垃圾收集器

垃圾收集器是區分年輕代和老年代的

Serial

採用復制演算法的年輕代收集器,單線程進行垃圾回收,回收過程中需要STW(stop the world)

配合使用的老年代收集器:CMS,Serial Old

優點:簡單,高效,適用於單核環境下使用

缺點:無法利用現在機器都是多核的優勢

ParNew

Serial的多線程版本,除多線程進行垃圾回收以外,其餘均和Serial一致

配合使用的老年代收集器:CMS,Serial Old

優點:利用了多核的功能(在單核環境下,Serial是比ParNew快的)

缺點:回收過程中需要STW,當需要回收的過多時,停頓時間會較長

Parallel Scavenge

採用復制演算法的年輕代收集器,多線程進行垃圾回收,與ParNew的區別在於它關注了系統的吞吐量

吞吐量 = 運行用戶代碼的時間 / (運行用戶代碼的時間 + GC的時間) 高吞吐量的應用適合後台執行的任務

此收集器有兩個參數:

-XX:MaxGCPauseMillis:設置垃圾回收的最大停頓時間

-XX:GCTimeRatio:設置垃圾回收時間的占總時間的比率

設置-XX:MaxGCPauseMillis可以提高響應速度,設置-XX:GCTimeRatio可以提高吞吐量,提高CPU的利用效率

另外此收集器還有另一個參數:-XX:UseAdaptiveSizePolicy,開啟的話,JVM會根據應用的運行狀態,動態設置eden區,survivor區,old區之間的比例,以提供最合適的停頓時間或最大的吞吐量。

Serial Old

Serial收集器的老年代版本,單線程,採用標記整理演算法

Parallel Old

Parallel Scavenge收集器的老年代版本,多線程,採用標記整理演算法

CMS(concurrent mark sweep)

以最短停頓時間為目的的收集器

初始標記:需要STW,標記老年代所有根對象。GC roots直接引用的對象及年輕代中存活對象引用的對象

並發標記:無需STW,標記所有存活對象。從上一階段找到的根對象開始,進行可達性分析。(JVM會提前把老年代邏輯劃分為大小相等的區域Card,因為應用程序並發執行,一些引用關系可能發生變化,,若引用關系發生改變,則將通過card marking將所在區域標記為臟區(dirty card))

最終標記:STW,重新掃描對象,進行重新標記

並發清除和重置:無需STW,清除所有垃圾,回收內存,重置CMS演算法的內部數據,為下一次GC做准備

在java 9中開始被啟用,在java 14中被刪除

優點:降低了STW的時間,提高更好的響應速度

缺點:

最大的問題是對老年代採用的是標記清除演算法,會產生大量內存碎片

對CPU資源敏感,因並發步驟較多,佔用一部分線程,降低吞吐量

無法處理浮動垃圾(並發清除時,可能會產生新的垃圾,而必須要等到下一次GC處理)

G1

G1邏輯上還保存著年輕代,老年代的概念。實現上,會把內存區域按照固定大小分成若干塊,叫做region。每一個region可以屬於eden區,也可以屬於old區,可動態變化。每次GC都會以region為單位進行回收。通過-XX:MaxGCPauseMills此參數,可設置每次STW最大停頓時間。eg. 當發生young gc時,G1根據最大停頓時間,盡量計算出可清理region的數量,可以不清除所有young區的region,進行GC,做到有效控制GC的停頓時間。

gc演算法從整體上來說是標記整理,從單個region上來說是標記復制,會將單個region的存活對象復制到一個空的region上去。

ZGC

停頓時間不會超過10ms

支持超大內存,達到4TB

故ZGC適用於大內存低延遲的服務

Shennandoah

CMS有個很大的問題就是採用的是標記-清除演算法,導致碎片化嚴重,思考一下為何CMS不採用標記整理演算法呢?其原因是CMS希望降低STW的時間,那麼如果用標記整理,那麼在整理時,對象的引用地址會發生變化,此時用戶線程是不能訪問的,那麼就得STW,把引用地址切換之後才能用。這樣就會提高STW,所以採用的標記清除。

同理G1在標記復制的時候,也會有STW的過程。而Shennandoah在復制時,採用了讀屏障和被稱為「Brooks Pointers」的轉發指針,避免掉STW過程。

垃圾收集器總結

關於垃圾收集器,我基本都是簡述,如果對某個收集器感興趣的話,可以做深入研究

收集器線程新/老演算法使用場景Serial單新生代復制單CPUParNew多新生代復制多CPUParallel Scavenge多新生代復制多CPU,以吞吐量為優先Serial Old單老年代標記整理單CPUParallel Old多老年代標記整理多CPU,以吞吐量為優先CMS多,可與用戶線程同時執行老年代標記清除多CPU,以減少STW為優先G1多,可與用戶線程同時執行新生代,老年代標記整理+復制多CPU,更精準的控制STW時間

常用組合:

新生代老年代SerialSerial OldParNewCMSParallel ScavengeParallel OldG1G1原文:https://juejin.cn/post/7101604290208202759
閱讀全文

與引用計數演算法相關的資料

熱點內容
必修一數學PDF 瀏覽:775
javascriptphpjsp 瀏覽:811
深圳一程序員退房完整版 瀏覽:294
後台管理app哪個好 瀏覽:766
加密鎖無模塊什麼意思 瀏覽:22
加密國度英文 瀏覽:20
科沃斯用了app怎麼使用按鍵 瀏覽:663
爬蟲編譯器價格 瀏覽:885
支付寶視頻app會員自動扣費怎麼關 瀏覽:230
單片機和wifi模塊 瀏覽:725
python倒序二進制代碼 瀏覽:362
廣東程序員離職 瀏覽:586
golang核心編程 瀏覽:170
中小學計演算法則 瀏覽:85
無職轉生哪個app能看 瀏覽:580
夏夢迪在哪個app 瀏覽:107
c編譯軟體cl 瀏覽:933
逗比的程序員頭像 瀏覽:984
單片機原理作業答案 瀏覽:230
文件夾的東西怎麼能轉成文檔 瀏覽:968