1.GC回收機制熟悉么,分代算法知道么?
2.了解 Java 虛擬機的垃圾回收算法?
從年輕代空間(包括 Eden 和 Survivor 區域)回收內存被稱為 Minor GC。 Major GC 是清理永久代。Full GC 是清理整個堆空間—包括年輕代和永久代。
> Android GC
Android GC 原理探究- http://geek.csdn.net/news/detail/193654
> Java中的GC是什么? 為什么要有GC?
GC目的:回收堆內存中不再使用的對象,釋放資源
回收時間:當對象永久地失去引用后,系統會在合適的時候回收它所占的內存。
GC System.gc()或Runtime.getRuntime().gc(),
忘記或者錯誤的內存回收會導致程序或系統的不穩定甚至崩潰,Java提供的GC功能可以自動監測對象是否超過作用域從而達到自動回收內存的目的。垃圾回收可以有效的防止內存泄露,有效的使用可以使用的內存。
采用“分代式垃圾收集”。這種方法會跟Java對象的生命周期將堆內存划分為不同的區域,在垃圾收集過程中,可能會將對象移動到不同區域:
- 伊甸園(Eden):這是對象最初誕生的區域,並且對大多數對象來說,這里是它們唯一存在過的區域。
- 幸存者樂園(Survivor):從伊甸園幸存下來的對象會被挪到這里。
- 終身頤養園(Tenured):這是足夠老的幸存對象的歸宿。年輕代收集(Minor-GC)過程是不會觸及這個地方的。當年輕代收集不能把對象放進終身頤養園時,就會觸發一次完全收集(Major-GC),這里可能還會牽扯到壓縮,以便為大對象騰出足夠的空間。
在C/C++中,釋放無用變量內存空間的事情要由程序員自己來解決。
Java有了GC,就不需要程序員去人工釋放內存空間。當Java虛擬機發覺內存資源緊張的時候,就會自動地去清理無用變量所占用的內存空間。當然,如果需要,程序員可以在Java程序中顯式地使用System.gc()來強制進行一次立即的內存清理。 因為顯式聲明是做堆內存全掃描,也就是 Full GC,是需要停止所有的活動的(Stop The World Collection),你的應用能承受這個嗎?而其顯示調用System.gc()只是給虛擬機一個建議,不一 定會執行,因為System.gc()在一個優先級很低的線程中執行。
java的GC功能可以自動監測對象是否超過作用域從而達到自動回收內存的目的,java語言沒有提供釋放已分配內存的俄顯示操作方法。在應用程序中,只要某對象變得不可達,也就是沒有根(root)引用該對象,這個對象就會成為垃圾回收器的目標。
GC的基本原理:
1.對於程序員來說,用new關鍵字即在堆中分配了內存,我們稱之為“可達”。對於GC來說,只要所有被引用的對象為null時,我們稱之為“不可達”,就將進行內存的回收。
2.當一個對象被創建時,GC開始監控這個對象的大小、內存地址及使用情況。GC采用有向圖的方式記錄和管理堆(heap)中的所有對象,通過這種方式可以明確哪些對象是可達的,哪些不是。當確定為不可達時,則對其進行回收。
3.保證GC在不同平台的實現問題,java規范對其很多行為沒有進行嚴格的規定。對於采用什么算法,什么時候進行回收等。
-- GC收集器有哪些?
1.serial收集器
單線程,工作時必須暫停其他工作線程。多用於client機器上,使用復制算法
2.ParNew收集器
serial收集器的多線程版本,server模式下虛擬機首選的新生代收集器。復制算法
3.Parallel Scavenge收集器
復制算法,可控制吞吐量的收集器。吞吐量即有效運行時間。
4.Serial Old收集器
serial的老年代版本,使用整理算法。
5.Parallel Old收集器
第三種收集器的老年代版本,多線程,標記整理
6.CMS收集器
目標是最短回收停頓時間。7.G1收集器,基本思想是化整為零,將堆分為多個Region,優先收集回收價值最大的Region。
> Java GC,GC 算法,GC機制
內存分配過程為(來源於《成為JavaGC專家part I》-http://www.importnew.com/1993.html
成為JavaGC專家Part II — 如何監控Java垃圾回收機制-http://www.importnew.com/2057.html
JDK5.0垃圾收集優化之 - http://calvin.iteye.com/blog/91905
三大Java 虛擬機垃圾回收機制的比較(HotSpot, JRockit, IBM JVM)- https://blog.csdn.net/ZYC88888/article/details/70918799
垃圾回收器,如何判斷對象是否存活- http://blog.csdn.net/TyroneRenekton/article/details/59114835?ref=myread
JVM初探- 內存分配、GC原理與垃圾收集器-- http://blog.csdn.net/zjf280441589/article/details/53946312
深入理解JVM之一:Java內存區域: https://yq.aliyun.com/articles/14408#
-- 在Java中,它的內存管理包括兩方面:內存分配(創建Java對象的時候)和內存回收。了解JVM,才能寫出更高效,充分利用有限的內存的程序。
Java垃圾回收主要做的是兩件事:1)內存回收; 2)碎片整理。
內存管理小技巧:
1)盡量使用直接量,eg:String javaStr = "小學徒的成長歷程";
2)使用StringBuilder和StringBuffer進行字符串連接等操作;
3)盡早釋放無用對象;
4)盡量少使用靜態變量;
5)緩存常用的對象:可以使用開源的開源緩存實現,eg:OSCache,Ehcache;
6)盡量不使用finalize()方法;
7)在必要的時候可以考慮使用軟引用SoftReference。
-- GC回收機制算法:分代復制垃圾回收和標記垃圾回收,增量垃圾回收。
學習Java GC機制,可以幫助我們在日常工作中排查各種內存溢出或泄露問題,解決性能瓶頸,達到更高的並發量,寫出更高效的程序。我們將從4個方面學習Java GC機制:
1,內存是如何分配的;
2,如何保證內存不被錯誤回收(即:哪些內存需要回收);
3,在什么情況下執行GC以及執行GC的方式;
4,如何監控和優化GC機制。
-- 了解Java GC機制,必須先清楚在JVM中內存區域的划分。
在Java運行時的數據區里,JVM管理的內存區域分為幾個模塊:
1,程序計數器(Program Counter Register):程序計數器是一個比較小的內存區域,用於指示當前線程所執行的字節碼執行到了第幾行,可以理解為是當前線程的行號指示器。字節碼解釋器在工作時,會通過改變這個計數器的值來取下一條語句指令。
每個程序計數器只用來記錄一個線程的行號,所以它是線程私有(一個線程就有一個程序計數器)的。
如果程序執行的是一個Java方法,則計數器記錄的是正在執行的虛擬機字節碼指令地址;如果正在執行的是一個本地(native,由C語言編寫 完成)方法,則計數器的值為Undefined,由於程序計數器只是記錄當前指令地址,所以不存在內存溢出的情況,因此,程序計數器也是所有JVM內存區 域中唯一一個沒有定義OutOfMemoryError的區域。
2,虛擬機棧(JVM Stack):一個線程的每個方法在執行的同時,都會創建一個棧幀(Statck Frame),棧幀中存儲的有局部變量表、操作站、動態鏈接、方法出口等,當方法被調用時,棧幀在JVM棧中入棧,當方法執行完成時,棧幀出棧。
局部變量表中存儲着方法的相關局部變量,包括各種基本數據類型,對象的引用,返回地址等。在局部變量表中,只有long和double類型會占用2個局部變量空間(Slot,對於32位機器,一個Slot就是32個bit),其它都是1個Slot。需要注意的是,局部變量表是在編譯時就已經確定 好的,方法運行所需要分配的空間在棧幀中是完全確定的,在方法的生命周期內都不會改變。
虛擬機棧中定義了兩種異常,如果線程調用的棧深度大於虛擬機允許的最大深度,則拋出StatckOverFlowError(棧溢出);不過多 數Java虛擬機都允許動態擴展虛擬機棧的大小(有少部分是固定長度的),所以線程可以一直申請棧,知道內存不足,此時,會拋出 OutOfMemoryError(內存溢出)。
每個線程對應着一個虛擬機棧,因此虛擬機棧也是線程私有的。
3,本地方法棧(Native Method Statck):本地方法棧在作用,運行機制,異常類型等方面都與虛擬機棧相同,唯一的區別是:虛擬機棧是執行Java方法的,而本地方法棧是用來執行native方法的,在很多虛擬機中(如Sun的JDK默認的HotSpot虛擬機),會將本地方法棧與虛擬機棧放在一起使用。
本地方法棧也是線程私有的。
4,堆區(Heap):堆區是理解Java GC機制最重要的區域,沒有之一。在JVM所管理的內存中,堆區是最大的一塊,堆區也是Java GC機制所管理的主要內存區域,堆區由所有線程共享,在虛擬機啟動時創建。堆區的存在是為了存儲對象實例,原則上講,所有的對象都在堆區上分配內存(不過現代技術里,也不是這么絕對的,也有棧上直接分配的)。
一般的,根據Java虛擬機規范規定,堆內存需要在邏輯上是連續的(在物理上不需要),在實現時,可以是固定大小的,也可以是可擴展的,目前主流的虛擬機堆區都是可擴展的。如果在執行垃圾回收之后,仍沒有足夠的內存分配,也不能再擴展,將會拋出OutOfMemoryError:Java heap space異常。
“Java內存分配機制”。
5,方法區(Method Area):在Java虛擬機規范中,將方法區作為堆的一個邏輯部分來對待,但事實上,方法區並不是堆(Non-Heap);另外,不少人的博客中,將Java GC的分代收集機制分為3個代:青年代,老年代,永久代,這些作者將方法區定義為“永久代”,這是因為,對於之前的HotSpot Java虛擬機的實現方式中,將分代收集的思想擴展到了方法區,並將方法區設計成了永久代。不過,除HotSpot之外的多數虛擬機,並不將方法區當做永久代,HotSpot本身,也計划取消永久代。本文中,由於筆者主要使用Oracle JDK6.0,因此仍將使用永久代一詞。
方法區是各個線程共享的區域,用於存儲已經被虛擬機加載的類信息(即加載類時需要加載的信息,包括版本、field、方法、接口等信息)、final常量、靜態變量、編譯器即時編譯的代碼等。
方法區在物理上也不需要是連續的,可以選擇固定大小或可擴展大小,並且方法區比堆還多了一個限制:可以選擇是否執行垃圾收集。一般的,方法區上 執行的垃圾收集是很少的,這也是方法區被稱為永久代的原因之一(HotSpot),但這也不代表着在方法區上完全沒有垃圾收集,其上的垃圾收集主要是針對常量池的內存回收和對已加載類的卸載。
在方法區上進行垃圾收集,條件苛刻而且相當困難,效果也不令人滿意,所以一般不做太多考慮,可以留作以后進一步深入研究時使用。
在方法區上定義了OutOfMemoryError:PermGen space異常,在內存不足時拋出。
運行時常量池(Runtime Constant Pool)是方法區的一部分,用於存儲編譯期就生成的字面常量、符號引用、翻譯出來的直接引用(符號引用就是編碼是用字符串表示某個變量、接口的位置,直接引用就是根據符號引用翻譯出來的地址,將在類鏈接階段完成翻譯);運行時常量池除了存儲編譯期常量外,也可以存儲在運行時間產生的常量(比如String類的intern()方法,作用是String維護了一個常量池,如果調用的字符“abc”已經在常量池中,則返回池中的字符串地址,否則,新建一個常量加入池中,並返回地址)。
6,直接內存(Direct Memory):直接內存並不是JVM管理的內存,可以這樣理解,直接內存,就是 JVM以外的機器內存,比如,你有4G的內存,JVM占用了1G,則其余的3G就是直接內存,JDK中有一種基於通道(Channel)和緩沖區 (Buffer)的內存分配方式,將由C語言實現的native函數庫分配在直接內存中,用存儲在JVM堆中的DirectByteBuffer來引用。 由於直接內存收到本機器內存的限制,所以也可能出現OutOfMemoryError的異常。
-- Java對象的訪問方式。一般來說,一個Java的引用訪問涉及到3個內存區域:JVM棧,堆,方法區。
以最簡單的本地變量引用:Object obj = new Object()為例:
Object obj表示一個本地引用,存儲在JVM棧的本地變量表中,表示一個reference類型數據;
new Object()作為實例對象數據存儲在堆中;
堆中還記錄了Object類的類型信息(接口、方法、field、對象類型等)的地址,這些地址所執行的數據存儲在方法區中;
在Java虛擬機規范中,對於通過reference類型引用訪問具體對象的方式並未做規定.
-- Java GC機制:
JVM 內置的通用垃圾回收原則。堆內存划分為 Eden、Survivor(年輕代) , Tenured/Old (老年代)空間:
1.Minor GC
從年輕代空間(包括 Eden 和 Survivor 區域)回收內存被稱為 Minor GC。但是,當發生Minor GC事件的時候,有一些有趣的地方需要注意到:
當 JVM 無法為一個新的對象分配空間時會觸發 Minor GC,比如當 Eden 區滿了。所以分配率越高,越頻繁執行 Minor GC。內存池被填滿的時候,其中的內容全部會被復制,指針會從0開始跟蹤空閑內存。Eden 和 Survivor 區進行了標記和復制操作,取代了經典的標記、掃描、壓縮、清理操作。所以 Eden 和 Survivor 區不存在內存碎片。寫指針總是停留在所使用內存池的頂部。執行 Minor GC 操作時,不會影響到永久代。從永久代到年輕代的引用被當成 GC roots,從年輕代到永久代的引用在標記階段被直接忽略掉。
質疑常規的認知,所有的 Minor GC 都會觸發“全世界的暫停(stop-the-world)”,停止應用程序的線程。對於大部分應用程序,停頓導致的延遲都是可以忽略不計的。其中的真相就 是,大部分 Eden 區中的對象都能被認為是垃圾,永遠也不會被復制到 Survivor 區或者老年代空間。如果正好相反,Eden 區大部分新生對象不符合 GC 條件,Minor GC 執行時暫停的時間將會長很多。
所以 Minor GC 的情況就相當清楚了——每次 Minor GC 會清理年輕代的內存。
2.Major GC vs Full GC
Major GC 是清理永久代。Full GC 是清理整個堆空間—包括年輕代和永久代。
很不幸,實際上它還有點復雜且令人困惑。首先,許多 Major GC 是由 Minor GC 觸發的,所以很多情況下將這兩種 GC 分離是不太可能的。另一方面,許多現代垃圾收集機制會清理部分永久代空間,所以使用“cleaning”一詞只是部分正確。
這使得我們不用去關心到底是叫 Major GC 還是 Full GC,大家應該關注當前的 GC 是否停止了所有應用程序的線程,還是能夠並發的處理而不用停掉應用程序的線程。
這種混亂甚至內置到 JVM 標准工具。下面一個例子很好的解釋了我的意思。讓我們比較兩個不同的工具 Concurrent Mark 和 Sweep collector (-XX:+UseConcMarkSweepGC)在 JVM 中運行時輸出的跟蹤記錄。
關於JVM,需要說明一下的是,目前使用最多的Sun公司的JDK中,自從 1999年的JDK1.2開始直至現在仍在廣泛使用的JDK6,其中默認的虛擬機都是HotSpot。2009年,Oracle收購Sun,加上之前收購 的EBA公司,Oracle擁有3大虛擬機中的兩個:JRockit和HotSpot,Oracle也表明了想要整合兩大虛擬機的意圖,但是目前在新發布 的JDK7中,默認的虛擬機仍然是HotSpot,因此本文中默認介紹的虛擬機都是HotSpot,相關機制也主要是指HotSpot的GC機制。
-- GC機制的基本算法是:分代收集
新生代:一般是指大批對象產生的快,消亡的也快;老生代:一般是指大批對象產生后,不容易消。
-- 年輕代:
新生代的主要垃圾回收方法,在新生代中,使用“停止-復制”算法進行清理,將新生代內存分為2部分,1部分 Eden區較大,1部分Survivor比較小,並被划分為兩個等量的部分。每次進行清理時,將Eden區和一個Survivor中仍然存活的對象拷貝到 另一個Survivor中,然后清理掉Eden和剛才的Survivor。
這里也可以發現,停止復制算法中,用來復制的兩部分並不總是相等的(傳統的停止復制算法兩部分內存相等,但新生代中使用1個大的Eden區和2個小的Survivor區來避免這個問題)
由於絕大部分的對象都是短命的,甚至存活不到Survivor中,所以,Eden區與Survivor的比例較大,HotSpot默認是 8:1,即分別占新生代的80%,10%,10%。如果一次回收中,Survivor+Eden中存活下來的內存超過了10%,則需要將一部分對象分配到 老年代。用-XX:SurvivorRatio參數來配置Eden區域Survivor區的容量比值,默認是8,代表Eden:Survivor1:Survivor2=8:1:1.
-- 老年代:
老年代存儲的對象比年輕代多得多,而且不乏大對象,對老年代進行內存清理時,如果使用停止-復制算法,則相當低效。一般,老年代用的算法是標記-整理算法,即:標記出仍然存活的對象(存在引用的),將所有存活的對象向一端移動,以保證內存的連續。
在發生Minor GC時,虛擬機會檢查每次晉升進入老年代的大小是否大於老年代的剩余空間大小,如果大於,則直接觸發一次Full GC,否則,就查看是否設 置了-XX:+HandlePromotionFailure(允許擔保失敗),如果允許,則只會進行MinorGC,此時可以容忍內存分配失敗;如果不允許,則仍然進行Full GC(這代表着如果設置-XX:+Handle PromotionFailure,則觸發MinorGC就會同時觸發Full GC,哪怕老年代還有很多內存,所以,最好不要這樣做)。
方法區(永久代),永久代的回收有兩種:常量池中的常量,無用的類信息,常量的回收很簡單,沒有引用了就可以被回收。對於無用的類進行回收,必須保證3點:1.類的所有實例都已經被回收;2.加載類的ClassLoader已經被回收。3.類對象的Class對象沒有被引用(即沒有通過反射引用該類的地方)
永久代的回收並不是必須的,可以通過參數來設置是否對類進行回收。HotSpot提供-Xnoclassgc進行控制,使用-verbose,-XX:+TraceClassLoading、-XX:+TraceClassUnLoading可以查看類加載和卸載信息:
-verbose、-XX:+TraceClassLoading可以在Product版HotSpot中使用;
-XX:+TraceClassUnLoading需要fastdebug版HotSpot支持。
-- 垃圾收集器
在介紹垃圾收集器之前,需要明確一點,就是在新生代采用的停止復制算法中,“停 止(Stop-the-world)”的意義是在回收內存時,需要暫停其他所有線程的執行。這個是很低效的,現在的各種新生代收集器越來越優化這一點,但仍然只是將停止的時間變短,並未徹底取消停止。
1. Serial收集器:新生代收集器,使用停止復制算法,使用一個線程進行GC,其它工作線程暫停。使用-XX:+UseSerialGC可以使用Serial+Serial Old模式運行進行內存回收(這也是虛擬機在Client模式下運行的默認值)
2. ParNew收集器:新生代收集器,使用停止復制算法,Serial收集器的多線程版,用多個線程進行GC,其它工作線程暫停,關注縮短垃圾收集時間。使用-XX:+UseParNewGC開關來控制使用ParNew+Serial Old收集器組合收集內存;使用-XX:ParallelGCThreads來設置執行內存回收的線程數。
3. Parallel Scavenge 收集器:新生代收集器,使用停止復制算法,關注CPU吞吐量,即運行用戶代碼的時間/總時間,比如:JVM運行100分鍾,其中運行用戶代碼99分鍾,垃圾收集1分鍾,則吞吐量是99%,這種收集器能最高效率的利用CPU,適合運行后台運算(關注縮短垃圾收集時間的收集器,如CMS,等待時間很少,所以適 合用戶交互,提高用戶體驗)。使用-XX:+UseParallelGC開關控制使用 Parallel Scavenge+Serial Old收集器組合回收垃圾(這也是在Server模式下的默認值);使用-XX:GCTimeRatio來設置用戶執行時間占總時間的比例,默認99,即 1%的時間用來進行垃圾回收。使用-XX:MaxGCPauseMillis設置GC的最大停頓時間(這個參數只對Parallel Scavenge有效)
4. Serial Old收集器:老年代收集器,單線程收集器,使用標記整理(整理的方法是Sweep清理和Compact壓縮,清理是將廢棄的對象干掉,只留幸存的對象,壓縮是將移動對象,將空間填滿保證內存分為2塊,一塊全是對象,一塊空閑)算法,使用單線程進行GC,其它工作線程暫停(注意,在老年代中進行標 記整理算法清理,也需要暫停其它線程),在JDK1.5之前,Serial Old收集器與ParallelScavenge搭配使用。
5. Parallel Old收集器:老年代收集器,多線程,多線程機制與Parallel Scavenge差不錯,使用標記整理(與Serial Old不同,這里的整理是Summary匯總和Compact壓縮,匯總的意思就是將幸存的對象復制到預先准備好的區域,而不是像Sweep(清 理)那樣清理廢棄的對象)算法,在Parallel Old執行時,仍然需要暫停其它線程。Parallel Old在多核計算中很有用。Parallel Old出現后(JDK 1.6),與Parallel Scavenge配合有很好的效果,充分體現Parallel Scavenge收集器吞吐量優先的效果。使用-XX:+UseParallelOldGC開關控制使用Parallel Scavenge +Parallel Old組合收集器進行收集。
6. CMS(Concurrent Mark Sweep)收集器:老年代收集器,致力於獲取最短回收停頓時間,使用標記清除算法,多線程,優點是並發收集(用戶線程可以和GC線程同時工作),停頓小。使用-XX:+UseConcMarkSweepGC進行ParNew+CMS+Serial Old進行內存回收,優先使用ParNew+CMS(原因見后面),當用戶線程內存不足時,采用備用方案Serial Old收集。
CMS收集的方法是:先3次標記,再1次清除,3次標記中前兩次是初始標記和重新標記(此時仍然需要停止(stop the world)), 初始標記(Initial Remark)是標記GC Roots能關聯到的對象(即有引用的對象),停頓時間很短;並發標記(Concurrent remark)是執行GC Roots查找引用的過程,不需要用戶線程停頓;重新標記(Remark)是在初始標記和並發標記期間,有標記變動的那部分仍需要標記,所以加上這一部分 標記的過程,停頓時間比並發標記小得多,但比初始標記稍長。在完成標記之后,就開始並發清除,不需要用戶線程停頓。
所以在CMS清理過程中,只有初始標記和重新標記需要短暫停頓,並發標記和並發清除都不需要暫停用戶線程,因此效率很高,很適合高交互的場合。
CMS也有缺點,它需要消耗額外的CPU和內存資源,在CPU和內存資源緊張,CPU較少時,會加重系統負擔(CMS默認啟動線程數為(CPU數量+3)/4)。
另外,在並發收集過程中,用戶線程仍然在運行,仍然產生內存垃圾,所以可能產生“浮動垃圾”,本次無法清理,只能下一次Full GC才清理,因此在GC期間,需要預留足夠的內存給用戶線程使用。所以使用CMS的收集器並不是老年代滿了才觸發Full GC,而是在使用了一大半(默認68%,即2/3,使用-XX:CMSInitiatingOccupancyFraction來設置)的時候就要進行Full GC,如果用戶線程消耗內存不是特別大,可以適當調高-XX:CMSInitiatingOccupancyFraction以降低GC次數,提高性能,如果預留的用戶線程內存不夠,則會觸發Concurrent Mode Failure,此時,將觸發備用方案:使用Serial Old 收集器進行收集,但這樣停頓時間就長了,因此-XX:CMSInitiatingOccupancyFraction不宜設的過大。
還有,CMS采用的是標記清除算法,會導致內存碎片的產生,可以使用-XX:+UseCMSCompactAtFullCollection來設置是否在Full GC之后進行碎片整理,用-XX:CMSFullGCsBeforeCompaction來設置在執行多少次不壓縮的Full GC之后,來一次帶壓縮的Full GC。
G1收集器:在JDK1.7中正式發布,與現狀的新生代、老年代概念有很大不同,目前使用較少,不做介紹。
注意並發(Concurrent)和並行(Parallel)的區別:
並發是指用戶線程與GC線程同時執行(不一定是並行,可能交替,但總體上是在同時執行的),不需要停頓用戶線程(其實在CMS中用戶線程還是需要停頓的,只是非常短,GC線程在另一個CPU上執行);
並行收集是指多個GC線程並行工作,但此時用戶線程是暫停的;所以,Serial和Parallel收集器都是並行的,而CMS收集器是並發的.
-- 垃圾回收器經典算法:
1)Reference counting(引用計數)
基本思想是:當對象創建並賦值時該對象的引用計數器置1,每當對象給任意變量賦值時,引用記數+1;一旦退出作用域則引用記數-1。一旦引用記數變為0,則該對象可以被垃圾回收。
引用記數有其相應的優勢:對程序的執行來說,每次操作只需要花費很小塊的時間。這對於不能被過長中斷的實時系統來說有着天然的優勢。
但也有其不足:不能夠檢測到環(兩個對象的互相引用);同時在每次增加或者減少引用記數的時候比較費時間。在現代的垃圾回收算法中,引用記數已經不再使用。
2)Mark-sweep(標記清理)
基本思想是:每次從根集出發尋找所有的引用(稱為活對象),每找到一個,則對其做出標記,當追蹤完成之后,所有的未標記對象便是需要回收的垃圾。
也叫追蹤算法,基於標記並清除。這個垃圾回收步驟分為兩個階段:在標記階段,垃圾回收器遍歷整棵引用樹並標記每一個遇到的對象。在清除階段,未標記的對象被釋放,並使其在內存中可用。
3)Copying collection(復制收集)
基本思想是:將內存划分為兩塊,一塊是當前正在使用;另一塊是當前未用。每次分配時使用當前正在使用內存,當無可用內存時,對該區域內存進行標記,並將標記的對象全部拷貝到當前未用內存區,這是反轉兩區域,即當前可用區域變為當前未用,而當前未用變為當前可用,繼續執行該算法。
拷貝算法需要停止所有的程序活動,然后開始冗長而繁忙的copy工作。這點是其不利的地方。
近年來還有兩種算法:
4)Generational garbage collection(分代)
其思想依據是:
(1) 被大多數程序創建的大多數對象有着非常短的生存期。
(2) 被大多數程序創建的部分對象有着非常長的生存期。
簡單拷貝算法的主要不足是它們花費了更多的時間去拷貝了一些長期生存的對象。
而分代算法的基本思想是:將內存區域分兩塊(或更多),其中一塊代表年輕代,另一塊代表老的一代。針對不同的特點,對年輕一代的垃圾收集更為頻繁,對老代的收集則較少,每次經過年輕一代的垃圾回收總會有未被收集的活對象,這些活對象經過收集之后會增加成熟度,當成熟度到達一定程度,則將其放進老代內存塊中。
分代算法很好的實現了垃圾回收的動態性,同時避免了內存碎片,是目前許多JVM使用的垃圾回收算法。
5)Conservative garbage collection(保守)