摘要:垃圾回收算法與垃圾回收器綜述我們常說的垃圾回收算法可以分為兩部分對象的查找算法與真正的回收方法。串行垃圾回收器一次只使用一個(gè)線程進(jìn)行垃圾回收并行垃圾回收器一次將開啟多個(gè)線程同時(shí)進(jìn)行垃圾回收。
垃圾回收算法與 JVM 垃圾回收器綜述垃圾回收算法與 JVM 垃圾回收器綜述歸納于筆者的 JVM 內(nèi)部原理與性能調(diào)優(yōu)系列文章,文中涉及的引用資料參考 Java 學(xué)習(xí)與實(shí)踐資料索引、JVM 資料索引。
我們常說的垃圾回收算法可以分為兩部分:對象的查找算法與真正的回收方法。不同回收器的實(shí)現(xiàn)細(xì)節(jié)各有不同,但總的來說基本所有的回收器都會(huì)關(guān)注如下兩個(gè)方面:找出所有的存活對象以及清理掉所有的其它對象——也就是那些被認(rèn)為是廢棄或無用的對象。Java 虛擬機(jī)規(guī)范中對垃圾收集器應(yīng)該如何實(shí)現(xiàn)并沒有任何規(guī)定,因此不同的廠商、不同版本的虛擬機(jī)所提供的垃圾收集器都可能會(huì)有很大差別,并且一般都會(huì)提供參數(shù)供用戶根據(jù)自己的應(yīng)用特點(diǎn)和要求組合出各個(gè)年代所使用的收集器。其中最主流的四個(gè)垃圾回收器分別是:通常用于單 CPU 環(huán)境的 Serial GC、Throughput/Parallel GC、CMS GC、G1 GC。
當(dāng)我們在討論垃圾回收器時(shí),往往也會(huì)涉及到很多的概念;譬如并行(Parallel)與并發(fā)(Concurrent)、Minor GC 與 Major / Full GC。并行指多條垃圾收集線程并行工作,但此時(shí)用戶線程仍然處于等待狀態(tài);并發(fā)指用戶線程與垃圾收集線程同時(shí)執(zhí)行(但不一定是并行的,可能會(huì)交替執(zhí)行),用戶程序在繼續(xù)運(yùn)行,而垃圾收集程序運(yùn)行于另一個(gè)CPU上。Minor GC 指發(fā)生在新生代的垃圾收集動(dòng)作,因?yàn)镴ava對象大多都具備朝生夕滅的特性,所以Minor GC非常頻繁,一般回收速度也比較快;Major GC 指發(fā)生在老年代的GC,出現(xiàn)了Major GC,經(jīng)常會(huì)伴隨至少一次的Minor GC(但非絕對的,在Parallel Scavenge收集器的收集策略里就有直接進(jìn)行Major GC的策略選擇過程),Major GC的速度一般會(huì)比Minor GC慢10倍以上。從不同角度分析垃圾回收器,可以將其分為不同的類型:
分類標(biāo)準(zhǔn) | 描述 |
---|---|
線程數(shù) | 分為串行垃圾回收器和并行垃圾回收器。串行垃圾回收器一次只使用一個(gè)線程進(jìn)行垃圾回收;并行垃圾回收器一次將開啟多個(gè)線程同時(shí)進(jìn)行垃圾回收。在并行能力較強(qiáng)的 CPU 上,使用并行垃圾回收器可以縮短 GC 的停頓時(shí)間。 |
工作模式 | 分為并發(fā)式垃圾回收器和獨(dú)占式垃圾回收器。并發(fā)式垃圾回收器與應(yīng)用程序線程交替工作,以盡可能減少應(yīng)用程序的停頓時(shí)間;獨(dú)占式垃圾回收器 (Stop the world) 一旦運(yùn)行,就停止應(yīng)用程序中的其他所有線程,直到垃圾回收過程完全結(jié)束。 |
碎片處理方式 | 分為壓縮式垃圾回收器和非壓縮式垃圾回收器。壓縮式垃圾回收器會(huì)在回收完成后,對存活對象進(jìn)行壓縮整理,消除回收后的碎片;非壓縮式的垃圾回收器不進(jìn)行這步操作。 |
工作的內(nèi)存區(qū)間 | 新生代垃圾回收器和老年代垃圾回收器 |
我們最常用的評價(jià)垃圾回收器的指標(biāo)就是吞吐量與停頓時(shí)間,停頓時(shí)間越短就越適合需要與用戶交互的程序,良好的響應(yīng)速度能提升用戶的體驗(yàn);而高吞吐量則可以最高效率地利用 CPU 時(shí)間,盡快地完成程序的運(yùn)算任務(wù),主要適合在后臺(tái)運(yùn)算而不需要太多交互的任務(wù);具體的指標(biāo)列舉如下:
吞吐量:指在應(yīng)用程序的生命周期內(nèi),應(yīng)用程序所花費(fèi)的時(shí)間和系統(tǒng)總運(yùn)行時(shí)間的比值。系統(tǒng)總運(yùn)行時(shí)間=應(yīng)用程序耗時(shí)+GC 耗時(shí)。如果系統(tǒng)運(yùn)行了 100min,GC 耗時(shí) 1min,那么系統(tǒng)的吞吐量就是 (100-1)/100=99%。
垃圾回收器負(fù)載:和吞吐量相反,垃圾回收器負(fù)載指來記回收器耗時(shí)與系統(tǒng)運(yùn)行總時(shí)間的比值。
停頓時(shí)間:指垃圾回收器正在運(yùn)行時(shí),應(yīng)用程序的暫停時(shí)間。對于獨(dú)占回收器而言,停頓時(shí)間可能會(huì)比較長。使用并發(fā)的回收器時(shí),由于垃圾回收器和應(yīng)用程序交替運(yùn)行,程序的停頓時(shí)間會(huì)變短,但是,由于其效率很可能不如獨(dú)占垃圾回收器,故系統(tǒng)的吞吐量可能會(huì)較低。
垃圾回收頻率:指垃圾回收器多長時(shí)間會(huì)運(yùn)行一次。一般來說,對于固定的應(yīng)用而言,垃圾回收器的頻率應(yīng)該是越低越好。通常增大堆空間可以有效降低垃圾回收發(fā)生的頻率,但是可能會(huì)增加回收產(chǎn)生的停頓時(shí)間。
反應(yīng)時(shí)間:指當(dāng)一個(gè)對象被稱為垃圾后多長時(shí)間內(nèi),它所占據(jù)的內(nèi)存空間會(huì)被釋放。
堆分配:不同的垃圾回收器對堆內(nèi)存的分配方式可能是不同的。一個(gè)良好的垃圾回收器應(yīng)該有一個(gè)合理的堆內(nèi)存區(qū)間劃分。
在對象查找算法的幫助下我們可以找到內(nèi)存可以被使用的,或者說那些內(nèi)存是可以回收,更多的時(shí)候我們肯定愿意做更少的事情達(dá)到同樣的目的。
對象引用在 JDK 1.2 以前的版本中,若一個(gè)對象不被任何變量引用,那么程序就無法再使用這個(gè)對象。也就是說,只有對象處于可觸及(Reachable)狀態(tài),程序才能使用它。從 JDK 1.2 版本開始,把對象的引用分為 4 種級別,從而使程序能更加靈活地控制對象的生命周期。這 4 種級別由高到低依次為:強(qiáng)引用、軟引用、弱引用和虛引用。
StrongReference: 強(qiáng)引用強(qiáng)引用是使用最普遍的引用。如果一個(gè)對象具有強(qiáng)引用,那垃圾回收器絕不會(huì)回收它。當(dāng)內(nèi)存空間不足,Java 虛擬機(jī)寧愿拋出 OutOfMemoryError 錯(cuò)誤,使程序異常終止,也不會(huì)靠隨意回收具有強(qiáng)引用的對象來解決內(nèi)存不足的問題。比如下面這段代碼:
public class Main { public static void main(String[] args) { new Main().fun1(); } public void fun1() { Object object = new Object(); Object[] objArr = new Object[1000]; } }
當(dāng)運(yùn)行至 Object[] objArr = new Object[1000]; 這句時(shí),如果內(nèi)存不足,JVM 會(huì)拋出 OOM 錯(cuò)誤也不會(huì)回收 object 指向的對象。不過要注意的是,當(dāng) fun1 運(yùn)行完之后,object 和 objArr 都已經(jīng)不存在了,所以它們指向的對象都會(huì)被 JVM 回收。如果想中斷強(qiáng)引用和某個(gè)對象之間的關(guān)聯(lián),可以顯示地將引用賦值為null,這樣一來的話,JVM在合適的時(shí)間就會(huì)回收該對象。比如 Vector 類的 clear 方法中就是通過將引用賦值為 null 來實(shí)現(xiàn)清理工作的:
/** * Removes the element at the specified position in this Vector. * Shifts any subsequent elements to the left (subtracts one from their * indices). Returns the element that was removed from the Vector. * * @throws ArrayIndexOutOfBoundsException if the index is out of range * ({@code index < 0 || index >= size()}) * @param index the index of the element to be removed * @return element that was removed * @since 1.2 */ public synchronized E remove(int index) { modCount++; if (index >= elementCount) throw new ArrayIndexOutOfBoundsException(index); Object oldValue = elementData[index]; int numMoved = elementCount - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--elementCount] = null; // Let gc do its work return (E)oldValue; }SoftReference: 軟引用
軟引用是用來描述一些有用但并不是必需的對象,在 Java 中用 java.lang.ref.SoftReference 類來表示。對于軟引用關(guān)聯(lián)著的對象,只有在內(nèi)存不足的時(shí)候 JVM 才會(huì)回收該對象。因此,這一點(diǎn)可以很好地用來解決 OOM 的問題,并且這個(gè)特性很適合用來實(shí)現(xiàn)緩存:比如網(wǎng)頁緩存、圖片緩存等。軟引用可以和一個(gè)引用隊(duì)列(ReferenceQueue)聯(lián)合使用,如果軟引用所引用的對象被JVM回收,這個(gè)軟引用就會(huì)被加入到與之關(guān)聯(lián)的引用隊(duì)列中。下面是一個(gè)使用示例:
import java.lang.ref.SoftReference; public class Main { public static void main(String[] args) { SoftReferenceWeakReference: 弱引用sr = new SoftReference (new String("hello")); System.out.println(sr.get()); } }
弱引用與軟引用的區(qū)別在于:只具有弱引用的對象擁有更短暫的生命周期。在垃圾回收器線程掃描它所管轄的內(nèi)存區(qū)域的過程中,一旦發(fā)現(xiàn)了只具有弱引用的對象,不管當(dāng)前內(nèi)存空間足夠與否,都會(huì)回收它的內(nèi)存。不過,由于垃圾回收器是一個(gè)優(yōu)先級很低的線程,因此不一定會(huì)很快發(fā)現(xiàn)那些只具有弱引用的對象。
import java.lang.ref.WeakReference; public class Main { public static void main(String[] args) { WeakReferencesr = new WeakReference (new String("hello")); System.out.println(sr.get()); System.gc(); //通知JVM的gc進(jìn)行垃圾回收 System.out.println(sr.get()); } }
輸出結(jié)果為:
hello null
第二個(gè)輸出結(jié)果是 null,這說明只要 JVM 進(jìn)行垃圾回收,被弱引用關(guān)聯(lián)的對象必定會(huì)被回收掉。不過要注意的是,這里所說的被弱引用關(guān)聯(lián)的對象是指只有弱引用與之關(guān)聯(lián),如果存在強(qiáng)引用同時(shí)與之關(guān)聯(lián),則進(jìn)行垃圾回收時(shí)也不會(huì)回收該對象(軟引用也是如此)。弱引用可以和一個(gè)引用隊(duì)列(ReferenceQueue)聯(lián)合使用,如果弱引用所引用的對象被垃圾回收,Java虛擬機(jī)就會(huì)把這個(gè)弱引用加入到與之關(guān)聯(lián)的引用隊(duì)列中。
PhantomReference: 虛引用“虛引用”顧名思義,就是形同虛設(shè),與其他幾種引用都不同,虛引用并不會(huì)決定對象的生命周期。如果一個(gè)對象僅持有虛引用,那么它就和沒有任何引用一樣,在任何時(shí)候都可能被垃圾回收器回收。虛引用和前面的軟引用、弱引用不同,它并不影響對象的生命周期。在 Java 中用 java.lang.ref.PhantomReference 類表示。如果一個(gè)對象與虛引用關(guān)聯(lián),則跟沒有引用與之關(guān)聯(lián)一樣,在任何時(shí)候都可能被垃圾回收器回收。要注意的是,虛引用必須和引用隊(duì)列關(guān)聯(lián)使用,當(dāng)垃圾回收器準(zhǔn)備回收一個(gè)對象時(shí),如果發(fā)現(xiàn)它還有虛引用,就會(huì)把這個(gè)虛引用加入到與之 關(guān)聯(lián)的引用隊(duì)列中。程序可以通過判斷引用隊(duì)列中是否已經(jīng)加入了虛引用,來了解被引用的對象是否將要被垃圾回收。如果程序發(fā)現(xiàn)某個(gè)虛引用已經(jīng)被加入到引用隊(duì)列,那么就可以在所引用的對象的內(nèi)存被回收之前采取必要的行動(dòng)。
import java.lang.ref.PhantomReference; import java.lang.ref.ReferenceQueue; public class Main { public static void main(String[] args) { ReferenceQueue對象存活性判斷queue = new ReferenceQueue (); PhantomReference pr = new PhantomReference (new String("hello"), queue); System.out.println(pr.get()); } }
常用的對象存活性判斷方法有引用計(jì)數(shù)法與可達(dá)性分析,不過由于引用計(jì)數(shù)法無法解決對象循環(huán)引用的問題,因此主流的 JVM 傾向于使用可達(dá)性分析。
Reference Counting: 引用計(jì)數(shù)引用計(jì)數(shù)器在微軟的 COM 組件技術(shù)中、Adobe 的 ActionScript3 種都有使用。引用計(jì)數(shù)器的原理很簡單,對于一個(gè)對象 A,只要有任何一個(gè)對象引用了 A,則 A 的引用計(jì)數(shù)器就加 1,當(dāng)引用失效時(shí),引用計(jì)數(shù)器就減 1。只要對象 A 的引用計(jì)數(shù)器的值為 0,則對象 A 就不可能再被使用。引用計(jì)數(shù)器的實(shí)現(xiàn)也非常簡單,只需要為每個(gè)對象配置一個(gè)整形的計(jì)數(shù)器即可。但是引用計(jì)數(shù)器有一個(gè)嚴(yán)重的問題,即無法處理循環(huán)引用的情況。因此,在 Java 的垃圾回收器中沒有使用這種算法。一個(gè)簡單的循環(huán)引用問題描述如下:有對象 A 和對象 B,對象 A 中含有對象 B 的引用,對象 B 中含有對象 A 的引用。此時(shí),對象 A 和對象 B 的引用計(jì)數(shù)器都不為 0。但是在系統(tǒng)中卻不存在任何第 3 個(gè)對象引用了 A 或 B。也就是說,A 和 B 是應(yīng)該被回收的垃圾對象,但由于垃圾對象間相互引用,從而使垃圾回收器無法識別,引起內(nèi)存泄漏。
所謂的引用樹本質(zhì)上是有根的圖結(jié)構(gòu),它沿著對象的根句柄向下查找到活著的節(jié)點(diǎn),并標(biāo)記下來;其余沒有被標(biāo)記的節(jié)點(diǎn)就是死掉的節(jié)點(diǎn),這些對象就是可以被回收的,或者說活著的節(jié)點(diǎn)就是可以被拷貝走的,具體要看所在 HeapSize中 的區(qū)域以及算法,它的大致示意圖如下圖所示(注意這里是指針是單向的):
首先,所有回收器都會(huì)通過一個(gè)標(biāo)記過程來對存活對象進(jìn)行統(tǒng)計(jì)。JVM 中用到的所有現(xiàn)代 GC 算法在回收前都會(huì)先找出所有仍存活的對象。下圖中所展示的JVM中的內(nèi)存布局可以用來很好地闡釋這一概念:
而所謂的GC根對象包括:當(dāng)前執(zhí)行方法中的所有本地變量及入?yún)?、活躍線程、已加載類中的靜態(tài)變量、JNI 引用。接下來,垃圾回收器會(huì)對內(nèi)存中的整個(gè)對象圖進(jìn)行遍歷,它先從 GC 根對象開始,然后是根對象引用的其它對象,比如實(shí)例變量。回收器將訪問到的所有對象都標(biāo)記為存活。存活對象在上圖中被標(biāo)記為藍(lán)色。當(dāng)標(biāo)記階段完成了之后,所有的存活對象都已經(jīng)被標(biāo)記完了。其它的那些(上圖中灰色的那些)也就是GC根對象不可達(dá)的對象,也就是說你的應(yīng)用不會(huì)再用到它們了。這些就是垃圾對象,回收器將會(huì)在接下來的階段中清除它們。
不過那些發(fā)現(xiàn)不能到達(dá) GC Roots 的對象并不會(huì)立即回收,在真正回收之前,對象至少要被標(biāo)記兩次。當(dāng)?shù)谝淮伪话l(fā)現(xiàn)不可達(dá)時(shí),該對象會(huì)被標(biāo)記一次,同時(shí)調(diào)用此對象的 finalize()方法(如果有);在第二次被發(fā)現(xiàn)不可達(dá)后,對象被回收。利用 finalisze() 方法,對象可以逃離一次被回收的命運(yùn),但是只有一次。逃命方法如下,需要在 finalize() 方法中給自己加一個(gè) GCRoots 中的 hook:
public class EscapeFromGC(){ public static EscapeFromGC hook; @Override protected void finalize() throws Throwable { super.finalize(); System.out.println("finalize mehtod executed!"); EscapeFromGC.hook = this; }通用垃圾回收算法
算法名 | 優(yōu)勢 | 缺陷 |
---|---|---|
Mark-Sweep / 標(biāo)記-清除 | 簡單 | 效率低下且會(huì)產(chǎn)生很多不連續(xù)內(nèi)存,分配大對象時(shí),容易提前引起另一次垃圾回收。 |
Copying / 復(fù)制 | 效率較高,不用考慮內(nèi)存碎片化 | 存在空間浪費(fèi) |
Mark-Compact / 標(biāo)記-整理 | 避免了內(nèi)存碎片化 | GC 暫停時(shí)間增長 |
標(biāo)記-清除算法將垃圾回收分為兩個(gè)階段:標(biāo)記階段和清除階段。一種可行的實(shí)現(xiàn)是,在標(biāo)記階段首先通過根節(jié)點(diǎn),標(biāo)記所有從根節(jié)點(diǎn)開始的較大對象。因此,未被標(biāo)記的對象就是未被引用的垃圾對象。然后,在清除階段,清除所有未被標(biāo)記的對象。該算法最大的問題是存在大量的空間碎片,因?yàn)榛厥蘸蟮目臻g是不連續(xù)的。在對象的堆空間分配過程中,尤其是大對象的內(nèi)存分配,不連續(xù)的內(nèi)存空間的工作效率要低于連續(xù)的空間。
從概念上來講,標(biāo)記-清除算法使用的方法是最簡單的,只需要忽略這些對象便可以了。也就是說當(dāng)標(biāo)記階段完成之后,未被訪問到的對象所在的空間都會(huì)被認(rèn)為是空閑的,可以用來創(chuàng)建新的對象。這種方法需要使用一個(gè)空閑列表來記錄所有的空閑區(qū)域以及大小。對空閑列表的管理會(huì)增加分配對象時(shí)的工作量。這種方法還有一個(gè)缺陷就是——雖然空閑區(qū)域的大小是足夠的,但卻可能沒有一個(gè)單一區(qū)域能夠滿足這次分配所需的大小,因此本次分配還是會(huì)失?。ㄔ贘ava中就是一次 OutOfMemoryError)。
Copying: 復(fù)制算法將現(xiàn)有的內(nèi)存空間分為兩快,每次只使用其中一塊,在垃圾回收時(shí)將正在使用的內(nèi)存中的存活對象復(fù)制到未被使用的內(nèi)存塊中,之后,清除正在使用的內(nèi)存塊中的所有對象,交換兩個(gè)內(nèi)存的角色,完成垃圾回收。如果系統(tǒng)中的垃圾對象很多,復(fù)制算法需要復(fù)制的存活對象數(shù)量并不會(huì)太大。因此在真正需要垃圾回收的時(shí)刻,復(fù)制算法的效率是很高的。又由于對象在垃圾回收過程中統(tǒng)一被復(fù)制到新的內(nèi)存空間中,因此,可確保回收后的內(nèi)存空間是沒有碎片的。該算法的缺點(diǎn)是將系統(tǒng)內(nèi)存折半。
Java 的新生代串行垃圾回收器中使用了復(fù)制算法的思想。新生代分為 eden 空間、from 空間、to 空間 3 個(gè)部分。其中 from 空間和 to 空間可以視為用于復(fù)制的兩塊大小相同、地位相等,且可進(jìn)行角色互換的空間塊。from 和 to 空間也稱為 survivor 空間,即幸存者空間,用于存放未被回收的對象。在垃圾回收時(shí),eden 空間中的存活對象會(huì)被復(fù)制到未使用的 survivor 空間中 (假設(shè)是 to),正在使用的 survivor 空間 (假設(shè)是 from) 中的年輕對象也會(huì)被復(fù)制到 to 空間中 (大對象,或者老年對象會(huì)直接進(jìn)入老年帶,如果 to 空間已滿,則對象也會(huì)直接進(jìn)入老年代)。此時(shí),eden 空間和 from 空間中的剩余對象就是垃圾對象,可以直接清空,to 空間則存放此次回收后的存活對象。這種改進(jìn)的復(fù)制算法既保證了空間的連續(xù)性,又避免了大量的內(nèi)存空間浪費(fèi)。
標(biāo)記-復(fù)制算法與標(biāo)記-整理算法非常類似,它們都會(huì)將所有存活對象重新進(jìn)行分配。區(qū)別在于重新分配的目標(biāo)地址不同,復(fù)制算法是為存活對象分配了另外的內(nèi)存 區(qū)域作為它們的新家。標(biāo)記復(fù)制算法的優(yōu)點(diǎn)在于標(biāo)記階段和復(fù)制階段可以同時(shí)進(jìn)行。它的缺點(diǎn)是需要一塊能容納下所有存活對象的額外的內(nèi)存空間。
Mark-Compact: 標(biāo)記-壓縮算法復(fù)制算法的高效性是建立在存活對象少、垃圾對象多的前提下的。這種情況在年輕代經(jīng)常發(fā)生,但是在老年代更常見的情況是大部分對象都是存活對象。如果依然使用復(fù)制算法,由于存活的對象較多,復(fù)制的成本也將很高。
標(biāo)記-壓縮算法是一種老年代的回收算法,它在標(biāo)記-清除算法的基礎(chǔ)上做了一些優(yōu)化。也首先需要從根節(jié)點(diǎn)開始對所有可達(dá)對象做一次標(biāo)記,但之后,它并不簡單地 清理未標(biāo)記的對象,而是將所有的存活對象壓縮到內(nèi)存的一端。之后,清理邊界外所有的空間。這種方法既避免了碎片的產(chǎn)生,又不需要兩塊相同的內(nèi)存空間,因此,其性價(jià)比比較高。
標(biāo)記-壓縮算法修復(fù)了標(biāo)記-清除算法的短板——它將所有標(biāo)記的也就是存活的對象都移動(dòng)到內(nèi)存區(qū)域的開始位置。這種方法的缺點(diǎn)就是GC暫停的時(shí)間會(huì)增 長,因?yàn)槟阈枰獙⑺械膶ο蠖伎截惖揭粋€(gè)新的地方,還得更新它們的引用地址。相對于標(biāo)記-清除算法,它的優(yōu)點(diǎn)也是顯而易見的——經(jīng)過整理之后,新對象的分 配只需要通過指針碰撞便能完成(pointer bumping),相當(dāng)簡單。使用這種方法空閑區(qū)域的位置是始終可知的,也不會(huì)再有碎片的問題了。
Incremental Collecting: 增量回收算法在垃圾回收過程中,應(yīng)用軟件將處于一種 CPU 消耗很高的狀態(tài)。在這種 CPU 消耗很高的狀態(tài)下,應(yīng)用程序所有的線程都會(huì)掛起,暫停一切正常的工作,等待垃圾回收的完成。如果垃圾回收時(shí)間過長,應(yīng)用程序會(huì)被掛起很久,將嚴(yán)重影響用戶體驗(yàn)或者系統(tǒng)的穩(wěn)定性。
增量算法現(xiàn)代垃圾回收的一個(gè)前身,其基本思想是,如果一次性將所有的垃圾進(jìn)行處理,需要造成系統(tǒng)長時(shí)間的停頓,那么就可以讓垃圾收集線程和應(yīng)用程序線程交替執(zhí)行。每次,垃圾收集線程只收集一小片區(qū)域的內(nèi)存空間,接著切換到應(yīng)用程序線程。依次反復(fù),直到垃圾收集完成。使用這種方式,由于在垃圾回收過程中,間斷性地還執(zhí)行了應(yīng)用程序代碼,所以能減少系統(tǒng)的停頓時(shí)間。但是,因?yàn)榫€程切換和上下文轉(zhuǎn)換的消耗,會(huì)使得垃圾回收的總體成本上升,造成系統(tǒng)吞吐量的下降。
Generational Collecting: 分代回收算法分代回收器是增量收集的另一個(gè)化身,根據(jù)垃圾回收對象的特性,不同階段最優(yōu)的方式是使用合適的算法用于本階段的垃圾回收,分代算法即是基于這種思想,它將內(nèi)存區(qū)間根據(jù)對象的特點(diǎn)分成幾塊,根據(jù) 每塊內(nèi)存區(qū)間的特點(diǎn),使用不同的回收算法,以提高垃圾回收的效率。以 Hot Spot 虛擬機(jī)為例,它將所有的新建對象都放入稱為年輕代的內(nèi)存區(qū)域,年輕代的特點(diǎn)是對象會(huì)很快回收,因此,在年輕代就選擇效率較高的復(fù)制算法。當(dāng)一個(gè)對象經(jīng)過幾 次回收后依然存活,對象就會(huì)被放入稱為老生代的內(nèi)存空間。在老生代中,幾乎所有的對象都是經(jīng)過幾次垃圾回收后依然得以幸存的。因此,可以認(rèn)為這些對象在一 段時(shí)期內(nèi),甚至在應(yīng)用程序的整個(gè)生命周期中,將是常駐內(nèi)存的。如果依然使用復(fù)制算法回收老生代,將需要復(fù)制大量對象。再加上老生代的回收性價(jià)比也要低于新 生代,因此這種做法也是不可取的。根據(jù)分代的思想,可以對老年代的回收使用與新生代不同的標(biāo)記-壓縮算法,以提高垃圾回收效率。
Concurrent Collecting: 并發(fā)回收算法所謂的并發(fā)回收算法即是指垃圾回收器與應(yīng)用程序能夠交替工作,并發(fā)回收 器其實(shí)也會(huì)暫停,但是時(shí)間非常短,它并不會(huì)在從開始回收尋找、標(biāo)記、清楚、壓縮或拷貝等方式過程完全暫停服務(wù),它發(fā)現(xiàn)有幾個(gè)時(shí)間比較長,一個(gè)就是標(biāo)記,因 為這個(gè)回收一般面對的是老年代,這個(gè)區(qū)域一般很大,而一般來說絕大部分對象應(yīng)該是活著的,所以標(biāo)記時(shí)間很長,還有一個(gè)時(shí)間是壓縮,但是壓縮并不一定非要每 一次做完GC都去壓縮的,而拷貝呢一般不會(huì)用在老年代,所以暫時(shí)不考慮;所以他們想出來的辦法就是:第一次短暫停機(jī)是將所有對象的根指針找到,這個(gè)非常容 易找到,而且非常快速,找到后,此時(shí)GC開始從這些根節(jié)點(diǎn)標(biāo)記活著的節(jié)點(diǎn)(這里可以采用并行),然后待標(biāo)記完成后,此時(shí)可能有新的 內(nèi)存申請以及被拋棄(java本身沒有內(nèi)存釋放這一概念),此時(shí)JVM會(huì)記錄下這個(gè)過程中的增量信息,而對于老年代來說,必須要經(jīng)過多次在 survivor倒騰后才會(huì)進(jìn)入老年代,所以它在這段時(shí)間增量一般來說會(huì)非常少,而且它被釋放的概率前面也說并不大(JVM如果不是完全做Cache,自 己做pageCache而且發(fā)生概率不大不小的pageout和pagein是不適合的);JVM根據(jù)這些增量信息快速標(biāo)記出內(nèi)部的節(jié)點(diǎn),也是非??焖?的,就可以開始回收了,由于需要?dú)⒌舻墓?jié)點(diǎn)并不多,所以這個(gè)過程也非??欤瑝嚎s在一定時(shí)間后會(huì)專門做一次操作,有關(guān)暫停時(shí)間在Hotspot版本,也就是 SUN的jdk中都是可以配置的,當(dāng)在指定時(shí)間范圍內(nèi)無法回收時(shí),JVM將會(huì)對相應(yīng)尺寸進(jìn)行調(diào)整,如果你不想讓它調(diào)整,在設(shè)置各個(gè)區(qū)域的大小時(shí),就使用定 量,而不要使用比例來控制;當(dāng)采用并發(fā)回收算法的時(shí)候,一般對于老年代區(qū)域,不會(huì)等待內(nèi)存小于10%左右的時(shí)候才會(huì)發(fā)起回收,因?yàn)椴l(fā)回收是允許在回收的 時(shí)候被分配,那樣就有可能來不及了,所以并發(fā)回收的時(shí)候,JVM可能會(huì)在68%左右的時(shí)候就開始啟動(dòng)對老年代GC了。
JVM 垃圾回收器對比1999 年隨 JDK1.3.1 一起來的是串行方式的 Serial GC,它是第一款垃圾回收器;此后,JDK1.4 和 J2SE1.3 相繼發(fā)布。2002 年 2 月 26 日,J2SE1.4 發(fā)布;Parallel GC 和Concurrent Mark Sweep (CMS)GC 跟隨 JDK1.4.2 一起發(fā)布,并且 Parallel GC 在 JDK6 之后成為 HotSpot 默認(rèn) GC。這三個(gè)垃圾回收器也是各有千秋,Serial GC 適合最小化地使用內(nèi)存和并行開銷的場景、Parallel GC 適合最大化應(yīng)用程序吞吐量的場景、CMS GC 適合最小化中斷或停頓時(shí)間的場景。上圖即展示了多種垃圾回收器之間的關(guān)系;不過隨著應(yīng)用程序所應(yīng)對的業(yè)務(wù)越來越龐大、復(fù)雜,用戶越來越多,沒有合適的回收器就不能保證應(yīng)用程序正常進(jìn)行,而經(jīng)常造成 STW 停頓的回收器又跟不上實(shí)際的需求,所以才會(huì)不斷地嘗試對搜集器進(jìn)行優(yōu)化。Garbage First(G1)GC 正是面向這種業(yè)務(wù)需求所生,它是一個(gè)并行回收器,把堆內(nèi)存分割為很多不相關(guān)的區(qū)間(Region);每個(gè)區(qū)間可以屬于老年代或者年輕代,并且每個(gè)年齡代區(qū)間可以是物理上不連續(xù)的。
名稱 | 作用域 | 算法 | 特性 | 設(shè)置 |
---|---|---|---|---|
Serial | Serial GC 作用于新生代,Serial Old GC 作用于老年代垃圾收集 | 二者皆采用了串行回收與 "Stop-the-World",Serial 使用的是復(fù)制算法,而 Serial Old 使用的是電俄式-標(biāo)記壓縮算法 | 基于串行回收的垃圾回收器適用于大多數(shù)對于暫停時(shí)間要求不高的 Client 模式下的 JVM | 使用 -XX:+UserSerialGC 手動(dòng)指定使用 Serial 回收器執(zhí)行內(nèi)存回收任務(wù) |
Throughput/Parallel | Parallel 作用于新生代,Parallel Old 作用于老年代 | 并行回收和 "Stop-the-World",Parallel 使用的是復(fù)制算法,Parallel Old 使用的是標(biāo)記-壓縮算法 | 程序吞吐量優(yōu)先的應(yīng)用場景中,在 Server 模式下內(nèi)存回收的性能較為不錯(cuò) | 使用 -XX:+UseParallelGC 手動(dòng)指定使用 Parallel 回收器執(zhí)行內(nèi)存回收任務(wù) |
CMS,Concurrent-Mark-Sweep | 老年代垃圾回收器,又稱作 Mostly-Concurrent 回收器 | 使用了標(biāo)記清除算法,分為初始標(biāo)記( Initial-Mark,Stop-the-World )、并發(fā)標(biāo)記( Concurrent-Mark )、再次標(biāo)記( Remark,Stop-the-World )、并發(fā)清除( Concurrent-Sweep ) | 并發(fā)低延遲,吞吐量較低。經(jīng)過CMS收集的堆會(huì)產(chǎn)生空間碎片,會(huì)帶來堆內(nèi)存的浪費(fèi) | 使用 -XX:+UseConcMarkSweepGC 來手動(dòng)指定使用 CMS 回收器執(zhí)行內(nèi)存回收任務(wù) |
G1,Garbage First | 沒有采用傳統(tǒng)物理隔離的新生代和老年代的布局方式,僅僅以邏輯上劃分為新生代和老年代,選擇的將 Java 堆區(qū)劃分為 2048 個(gè)大小相同的獨(dú)立 Region 塊 | 使用了標(biāo)記壓縮算法 | 基于并行和并發(fā)、低延遲以及暫停時(shí)間更加可控的區(qū)域化分代式服務(wù)器類型的垃圾回收器 | 使用 -XX:UseG1GC 來手動(dòng)指定使用 G1 回收器執(zhí)行內(nèi)存回收任務(wù) |
關(guān)于標(biāo)記階段有幾個(gè)關(guān)鍵點(diǎn)是值得注意的:
開始進(jìn)行標(biāo)記前,需要先暫停應(yīng)用線程,否則如果對象圖一直在變化的話是無法真正去遍歷它的。暫停應(yīng)用線程以便 JVM 可以盡情地收拾家務(wù)的這種情況又被稱之為安全點(diǎn)(Safe Point),這會(huì)觸發(fā)一次Stop The World(STW)暫停。觸發(fā)安全點(diǎn)的原因有許多,但最常見的應(yīng)該就是垃圾回收了。
暫停時(shí)間的長短并不取決于堆內(nèi)對象的多少也不是堆的大小,而是存活對象的多少。因此,調(diào)高堆的大小并不會(huì)影響到標(biāo)記階段的時(shí)間長短。
當(dāng)標(biāo)記階段完成后,GC開始進(jìn)入下一階段,刪除不可達(dá)對象。
Serial GC串行回收器主要有兩個(gè)特點(diǎn):第一,它僅僅使用單線程進(jìn)行垃圾回收;第二,它獨(dú)占式的垃圾回收。在串行回收器進(jìn)行垃圾回收時(shí),Java 應(yīng)用程序中的線程都需要暫停,等待垃圾回收的完成,這樣給用戶體驗(yàn)造成較差效果。雖然如此,串行回收器卻是一個(gè)成熟、經(jīng)過長時(shí)間生產(chǎn)環(huán)境考驗(yàn)的極為高效的 回收器。新生代串行處理器使用復(fù)制算法,實(shí)現(xiàn)相對簡單,邏輯處理特別高效,且沒有線程切換的開銷。在諸如單 CPU 處理器或者較小的應(yīng)用內(nèi)存等硬件平臺(tái)不是特別優(yōu)越的場合,它的性能表現(xiàn)可以超過并行回收器和并發(fā)回收器。在 HotSpot 虛擬機(jī)中,使用-XX:+UseSerialGC 參數(shù)可以指定使用新生代串行回收器和老年代串行回收器。當(dāng) JVM 在 Client 模式下運(yùn)行時(shí),它是默認(rèn)的垃圾回收器。老年代串行回收器使用的是標(biāo)記-壓縮算法。和新生代串行回收器一樣,它也是一個(gè)串行的、獨(dú)占式的垃圾回收器。由于老年代垃圾回收通常會(huì)使用比新生代垃圾回 收更長的時(shí)間,因此,在堆空間較大的應(yīng)用程序中,一旦老年代串行回收器啟動(dòng),應(yīng)用程序很可能會(huì)因此停頓幾秒甚至更長時(shí)間。雖然如此,老年代串行回收器可以 和多種新生代回收器配合使用,同時(shí)它也可以作為 CMS 回收器的備用回收器。若要啟用老年代串行回收器,可以嘗試使用以下參數(shù):-XX:+UseSerialGC: 新生代、老年代都使用串行回收器。
Serial GC 的工作步驟如下所示:
ParNew GC并行回收器是工作在新生代的垃圾回收器,它只簡單地將串行回收器多線程化。它的回收策略、算法以及參數(shù)和串行回收器一樣。
并行回收器 也是獨(dú)占式的回收器,在收集過程中,應(yīng)用程序會(huì)全部暫停。但由于并行回收器使用多線程進(jìn)行垃圾回收,因此,在并發(fā)能力比較強(qiáng)的 CPU 上,它產(chǎn)生的停頓時(shí)間要短于串行回收器,而在單 CPU 或者并發(fā)能力較弱的系統(tǒng)中,并行回收器的效果不會(huì)比串行回收器好,由于多線程的壓力,它的實(shí)際表現(xiàn)很可能比串行回收器差。開啟并行回收器可以使用參數(shù)-XX:+UseParNewGC,該參數(shù)設(shè)置新生代使用并行回收器,老年代使用串行回收器。老年代的并行回收回收器也是一種多線程并發(fā)的回收器。和新生代并行回收回收器一樣,它也是一種關(guān)注吞吐量的回收器。老年代并行回收回收器使用標(biāo)記-壓縮算法,JDK1.6 之后開始啟用。
Parallel Scavenge 收集器的特點(diǎn)是它的關(guān)注點(diǎn)與其他收集器不同,CMS 等收集器的關(guān)注點(diǎn)盡可能地縮短垃圾收集時(shí)用戶線程的停頓時(shí)間,而 Parallel Scavenge 收集器的目標(biāo)則是達(dá)到一個(gè)可控制的吞吐量(Throughput)。Parallel Old是Parallel Scavenge收集器的老年代版本,使用多線程和“標(biāo)記-整理”算法。這個(gè)收集器是在JDK 1.6中才開始提供的。使用 -XX:+UseParallelOldGC 可以在新生代和老生代都使用并行回收回收器,這是一對非常關(guān)注吞吐量的垃圾回收器組合,在對吞吐量敏感的系統(tǒng)中,可以考慮使用。參數(shù) -XX:ParallelGCThreads 也可以用于設(shè)置垃圾回收時(shí)的線程數(shù)量。
Parallel GC 的工作步驟如下所示:
CMS( Concurrent Mark-Sweep ) 是以犧牲吞吐量為代價(jià)來獲得最短回收停頓時(shí)間的垃圾回收器,適用于對停頓比較敏感,并且有相對較多存活時(shí)間較長的對象(老年代較大)的應(yīng)用程序;不過 CMS 雖然減少了回收的停頓時(shí)間,但是降低了堆空間的利用率。CMS GC 采用了 Mark-Sweep 算法,因此經(jīng)過CMS收集的堆會(huì)產(chǎn)生空間碎片;為了解決堆空間浪費(fèi)問題,CMS回收器不再采用簡單的指針指向一塊可用堆空間來為下次對象分配使用。而是把一些未分配的空間匯總成一個(gè)列表,當(dāng) JVM 分配對象空間的時(shí)候,會(huì)搜索這個(gè)列表找到足夠大的空間來存放住這個(gè)對象。另一方面,由于 CMS 線程和應(yīng)用程序線程并發(fā)執(zhí)行,CMS GC 需要更多的 CPU 資源。同時(shí),因?yàn)镃MS標(biāo)記階段應(yīng)用程序的線程還是在執(zhí)行的,那么就會(huì)有堆空間繼續(xù)分配的情況,為了保證在CMS回收完堆之前還有空間分配給正在運(yùn)行的應(yīng)用程序,必須預(yù)留一部分空間。也就是說,CMS不會(huì)在老年代滿的時(shí)候才開始收集。相反,它會(huì)嘗試更早的開始收集,已避免上面提到的情況:在回收完成之前,堆沒有足夠空間分配!默認(rèn)當(dāng)老年代使用68%的時(shí)候,CMS就開始行動(dòng)了。 – XX:CMSInitiatingOccupancyFraction =n 來設(shè)置這個(gè)閥值。
CMS GC 工作步驟如下所示:
初始標(biāo)記(STW initial mark):在這個(gè)階段,需要虛擬機(jī)停頓正在執(zhí)行的任務(wù),官方的叫法STW(Stop The Word)。這個(gè)過程從垃圾回收的"根對象"開始,只掃描到能夠和"根對象"直接關(guān)聯(lián)的對象,并作標(biāo)記。所以這個(gè)過程雖然暫停了整個(gè)JVM,但是很快就完成了。
并發(fā)標(biāo)記(Concurrent marking):這個(gè)階段緊隨初始標(biāo)記階段,在初始標(biāo)記的基礎(chǔ)上繼續(xù)向下追溯標(biāo)記。并發(fā)標(biāo)記階段,應(yīng)用程序的線程和并發(fā)標(biāo)記的線程并發(fā)執(zhí)行,所以用戶不會(huì)感受到停頓。
并發(fā)預(yù)清理(Concurrent precleaning):并發(fā)預(yù)清理階段仍然是并發(fā)的。在這個(gè)階段,虛擬機(jī)查找在執(zhí)行并發(fā)標(biāo)記階段新進(jìn)入老年代的對象(可能會(huì)有一些對象從新生代晉升到老年代,或者有一些對象被分配到老年代)。通過重新掃描,減少下一個(gè)階段"重新標(biāo)記"的工作,因?yàn)橄乱粋€(gè)階段會(huì)Stop The World。
重新標(biāo)記(STW remark):這個(gè)階段會(huì)暫停虛擬機(jī),回收器線程掃描在CMS堆中剩余的對象。掃描從"跟對象"開始向下追溯,并處理對象關(guān)聯(lián)。
并發(fā)清理(Concurrent sweeping):清理垃圾對象,這個(gè)階段回收器線程和應(yīng)用程序線程并發(fā)執(zhí)行。
并發(fā)重置(Concurrent reset):這個(gè)階段,重置CMS回收器的數(shù)據(jù)結(jié)構(gòu),等待下一次垃圾回收。
G1 GCG1 GC 是 JDK 1.7 中正式投入使用的用于取代 CMS 的壓縮回收器,它雖然沒有在物理上隔斷新生代與老生代,但是仍然屬于分代垃圾回收器;G1 GC 仍然會(huì)區(qū)分年輕代與老年代,年輕代依然分有 Eden 區(qū)與 Survivor 區(qū)。G1 GC 首先將堆分為大小相等的 Region,避免全區(qū)域的垃圾收集,然后追蹤每個(gè) Region 垃圾堆積的價(jià)值大小,在后臺(tái)維護(hù)一個(gè)優(yōu)先列表,根據(jù)允許的收集時(shí)間優(yōu)先回收價(jià)值最大的Region;同時(shí) G1 GC 采用 Remembered Set 來存放 Region 之間的對象引用以及其他回收器中的新生代與老年代之間的對象引用,從而避免全堆掃描。G1 GC 的分區(qū)示例如下圖所示:
隨著 G1 GC 的出現(xiàn),Java 垃圾回收器通過引入 Region 的概念,從傳統(tǒng)的連續(xù)堆內(nèi)存布局設(shè)計(jì),逐步走向了物理上不連續(xù)但是邏輯上依舊連續(xù)的內(nèi)存塊;這樣我們能夠?qū)⒛硞€(gè) Region 動(dòng)態(tài)地分配給 Eden、Survivor、老年代、大對象空間、空閑區(qū)間等任意一個(gè)。每個(gè) Region 都有一個(gè)關(guān)聯(lián)的 Remembered Set(簡稱RS),RS 的數(shù)據(jù)結(jié)構(gòu)是 Hash 表,里面的數(shù)據(jù)是 Card Table (堆中每 512byte 映射在 card table 1byte)。簡單的說RS里面存在的是Region中存活對象的指針。當(dāng)Region中數(shù)據(jù)發(fā)生變化時(shí),首先反映到Card Table中的一個(gè)或多個(gè)Card上,RS通過掃描內(nèi)部的Card Table得知Region中內(nèi)存使用情況和存活對象。在使用Region過程中,如果Region被填滿了,分配內(nèi)存的線程會(huì)重新選擇一個(gè)新的Region,空閑Region被組織到一個(gè)基于鏈表的數(shù)據(jù)結(jié)構(gòu)(LinkedList)里面,這樣可以快速找到新的Region。
總結(jié)而言,G1 GC 的特性如下:
并行性:G1在回收期間,可以有多個(gè)GC線程同時(shí)工作,有效利用多核計(jì)算能力;
并發(fā)性:G1擁有與應(yīng)用程序交替執(zhí)行的能力,部分工作可以和應(yīng)用程序同時(shí)執(zhí)行,因此,一般來說,不會(huì)在整個(gè)回收階段發(fā)生完全阻塞應(yīng)用程序的情況;
分代GC:G1依然是一個(gè)分代回收器,但是和之前的各類回收器不同,它同時(shí)兼顧年輕代和老年代。對比其他回收器,或者工作在年輕代,或者工作在老年代;
空間整理:G1在回收過程中,會(huì)進(jìn)行適當(dāng)?shù)膶ο笠苿?dòng),不像CMS只是簡單地標(biāo)記清理對象。在若干次GC后,CMS必須進(jìn)行一次碎片整理。而G1不同,它每次回收都會(huì)有效地復(fù)制對象,減少空間碎片,進(jìn)而提升內(nèi)部循環(huán)速度。
可預(yù)見性:為了縮短停頓時(shí)間,G1建立可預(yù)存停頓的模型,這樣在用戶設(shè)置的停頓時(shí)間范圍內(nèi),G1會(huì)選擇適當(dāng)?shù)膮^(qū)域進(jìn)行收集,確保停頓時(shí)間不超過用戶指定時(shí)間。
G1 GC 的工作步驟如下所示:
初始標(biāo)記(標(biāo)記一下GC Roots能直接關(guān)聯(lián)的對象并修改TAMS值,需要STW但耗時(shí)很短)
并發(fā)標(biāo)記(從GC Root從堆中對象進(jìn)行可達(dá)性分析找存活的對象,耗時(shí)較長但可以與用戶線程并發(fā)執(zhí)行)
最終標(biāo)記(為了修正并發(fā)標(biāo)記期間產(chǎn)生變動(dòng)的那一部分標(biāo)記記錄,這一期間的變化記錄在Remembered Set Log 里,然后合并到Remembered Set里,該階段需要STW但是可并行執(zhí)行)
篩選回收(對各個(gè)Region回收價(jià)值排序,根據(jù)用戶期望的GC停頓時(shí)間制定回收計(jì)劃來回收)
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/67537.html
摘要:在這種消耗很高的狀態(tài)下,應(yīng)用程序所有的線程都會(huì)掛起,暫停一切正常的工作,等待垃圾回收的完成。但是,因?yàn)榫€程切換和上下文轉(zhuǎn)換的消耗,會(huì)使得垃圾回收的總體成本上升,造成系統(tǒng)吞吐量的下降。 Java 垃圾回收(GC) 泛讀 文章地址: https://segmentfault.com/a/1190000008922319 0. 序言 帶著問題去看待 垃圾回收(GC) 會(huì)比較好,一般來說主要的...
垃圾回收(GC)是JVM的一大殺器,它使程序員可以更高效地專注于程序的開發(fā)設(shè)計(jì),而不用過多地考慮對象的創(chuàng)建銷毀等操作。但是這并不是說程序員不需要了解GC。GC只是Java編程中一項(xiàng)自動(dòng)化工具,任何一個(gè)工具都有它適用的范圍,當(dāng)超出它的范圍的時(shí)候,可能它將不是那么自動(dòng),而是需要人工去了解與適應(yīng)地適用。 擁有一定工作年限的程序員,在工作期間肯定會(huì)經(jīng)常碰到像內(nèi)存溢出、內(nèi)存泄露、高并發(fā)的場景。這時(shí)候在應(yīng)對這...
摘要:一次性編譯成機(jī)器碼,脫離開發(fā)環(huán)境獨(dú)立運(yùn)行,運(yùn)行效率較高。解釋型語言使用專門的解釋器對源程序逐行解釋成特定平臺(tái)的機(jī)器碼并立即執(zhí)行的語言。垃圾回收機(jī)制保護(hù)程序的完整性,垃圾回收是語言安全性策略的一個(gè)重要部分。 Java程序運(yùn)行機(jī)制 編譯型語言 使用專門的編譯器,針對特定平臺(tái)(操作系統(tǒng))將某種高級語言源代碼一次性翻譯成可被該平臺(tái)硬件執(zhí)行的機(jī)器碼(包括機(jī)器指令和操作數(shù)),并包裝成該平臺(tái)所能識...
摘要:這個(gè)算法看似不錯(cuò)而且簡單,不過存在這一個(gè)致命傷當(dāng)兩個(gè)對象互相引用的時(shí)候,就永遠(yuǎn)不會(huì)被回收于是引用計(jì)數(shù)算法就永遠(yuǎn)回收不了這兩個(gè)對象,下面介紹另一種算法。 前言 ? 如果要問Java與其他編程語言最大的不同是什么,我第一個(gè)想到的一定就是Java所運(yùn)行的JVM所自帶的自動(dòng)垃圾回收機(jī)制,以下是我學(xué)習(xí)JVM垃圾回收機(jī)制整理的筆記,希望能對讀者有一些幫助。 哪些內(nèi)存需要回收?what? ? ...
摘要:而使用虛擬機(jī)是實(shí)現(xiàn)這一特點(diǎn)的關(guān)鍵。每個(gè)字節(jié)碼指令都由一個(gè)字節(jié)的操作碼和附加的操作數(shù)組成。字節(jié)碼可以通過以下兩種方式轉(zhuǎn)換成合適的語言解釋器一條一條地讀取,解釋并執(zhí)行字節(jié)碼執(zhí)行,所以它可以很快地解釋字節(jié)碼,但是執(zhí)行起來會(huì)比較慢。 一、什么是JVM JVM是Java Virtual Machine(Java 虛擬機(jī))的縮寫,JVM是一種用于計(jì)算設(shè)備的規(guī)范,它是一個(gè)虛構(gòu)出來的計(jì)算機(jī),是通過在實(shí)...
閱讀 808·2021-11-17 09:33
閱讀 3853·2021-09-01 10:46
閱讀 1851·2019-08-30 11:02
閱讀 3357·2019-08-29 15:05
閱讀 1453·2019-08-26 11:39
閱讀 2358·2019-08-23 17:04
閱讀 2032·2019-08-23 15:43
閱讀 1425·2019-08-23 14:12