日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

(六)Java垃圾回收机制(附带代码示例)

發布時間:2023/12/10 java 41 豆豆
生活随笔 收集整理的這篇文章主要介紹了 (六)Java垃圾回收机制(附带代码示例) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

本篇注意圍繞Java垃圾回收當中的 哪些內存需要回收?什么時候回收?如何回收?這三個問題 結合著【深入理解Java虛擬機】一書當中整理了本篇博客,感興趣的跟著小編一塊來學習呀!

目錄

    • 一、概述
    • 二、對象已死?
      • 1、引用計數算法
      • 2、可達性分析算法
      • 3、四種引用
      • 4、生存還是死亡?
      • 5、回收方法區
    • 三、垃圾收集算法
      • 1、分代收集理論
      • 2、名詞解釋
      • 3、標記-清除算法
      • 4、標記-復制算法
      • 5、標記-整理算法

一、概述

說起垃圾收集(Garbage Collection,下文簡稱GC),有不少人把這項技術當作Java語言的伴生產 物。事實上,垃圾收集的歷史遠遠比Java久遠,在1960年誕生于麻省理工學院的Lisp是第一門開始使 用內存動態分配和垃圾收集技術的語言。當Lisp還在胚胎時期時,其作者John McCarthy就思考過垃圾 收集需要完成的三件事情:

  • 哪些內存需要回收?
  • 什么時候回收?
  • 如何回收?

經過半個世紀的發展,今天的內存動態分配與內存回收技術已經相當成熟,一切看起來都進入 了“自動化”時代,那為什么我們還要去了解垃圾收集和內存分配?

答案很簡單:當需要排查各種內存 溢出、內存泄漏問題時,當垃圾收集成為系統達到更高并發量的瓶頸時,我們就必須對這些“自動 化”的技術實施必要的監控和調節。

Java內存運 行時區域其中程序計數器、虛擬機棧、本地方法棧3個區域隨線程而生,隨線程而滅,棧 中的棧幀隨著方法的進入和退出而有條不紊地執行著出棧和入棧操作。每一個棧幀中分配多少內存基 本上是在類結構確定下來時就已知的,因此這幾個區域的內存分配和回收都具備確定性, 在這幾個區域內就不需要過多考慮如何回收的問題,當方法結束或者線程結束時,內存自然就跟隨著 回收了。

而Java堆和方法區這兩個區域則有著很顯著的不確定性:一個接口的多個實現類需要的內存可能 會不一樣,一個方法所執行的不同條件分支所需要的內存也可能不一樣,只有處于運行期間,我們才 能知道程序究竟會創建哪些對象,創建多少個對象,這部分內存的分配和回收是動態的。垃圾收集器 所關注的正是這部分內存該如何管理,本文后續討論中的“內存”分配與回收也僅僅特指這一部分內 存。

二、對象已死?

在堆里面存放著Java世界中幾乎所有的對象實例,垃圾收集器在對堆進行回收前,第一件事情就 是要確定這些對象之中哪些還“存活”著,哪些已經“死去”(“死去”即不可能再被任何途徑使用的對 象)了。

這個面試的時候也經常會問到,依靠兩個算法來判斷對象是否存活:
1、引用計數算法 2、可達分析算法。

1、引用計數算法

判斷對象是否存活的算法是這樣的:在對象中添加一個引用計數器,每當有一個地方 引用它時,計數器值就加一;當引用失效時,計數器值就減一;任何時刻計數器為零的對象就是不可 能再被使用的。

客觀地說,引用計數算法(Reference Counting)雖然占用了一些額外的內存空間來進行計數,但 它的原理簡單,判定效率也很高,很多領域都在用,但是,在Java 領域,至少主流的Java虛擬機里面都沒有選用引用計數算法來管理內存。原因:單純的引用計數 就很難解決對象之間相互循環引用的問題。

代碼示例:
在下面testGC()方法:對象objA和objB都有字段instance,賦值令 objA.instance=objB及objB.instance=objA,除此之外,這兩個對象再無任何引用,實際上這兩個對象已 經不可能再被訪問,但是它們因為互相引用著對方,導致它們的引用計數都不為零,引用計數算法也 就無法回收它們。

對于objA = null,objB = null應該有的人會不理解為什么要這么做,咱們要測試的是屬性出現了互相引用,是否會被gc掉。那么我就得保證其他地方不再引用他,我們用的是main方法測試,所以需要設置為null。正常在java應用是不需要的,線程執行過后,局部變量將被銷毀。那也就不存在引用這一說了。

雖然手動的讓局部變量不在引用堆中的對象,但是在堆內存當中這兩個對象的屬性還是相互引用的。如果按照計數算法,他是不應該被gc掉的。

public class ReferenceCountingGC {public Object instance = null;private static final int _1MB = 1024 * 1024;/*** 這個成員屬性的唯一意義就是占點內存,以便能在GC日志中看清楚是否有回收過 */private byte[] bigSize = new byte[2 * _1MB];public static void testGC() {ReferenceCountingGC objA = new ReferenceCountingGC();ReferenceCountingGC objB = new ReferenceCountingGC();objA.instance = objB;objB.instance = objA;objA = null;objB = null;// 假設在這行發生GC,objA和objB是否能被回收?System.gc();}public static void main(String[] args) {testGC();} }

設置啟動參數:輸出GC的詳細日志

-XX:+PrintGCDetails

運行結果:

從運行結果中可以清楚看到內存回收日志中包含“9257K->823K”,意味著虛擬機并沒有因為這兩 個對象互相引用就放棄回收它們,這也從側面說明了Java虛擬機并不是通過引用計數算法來判斷對象 是否存活的。

2、可達性分析算法

當前主流的商用程序語言(Java、C#,上溯至前面提到的古老的Lisp)的內存管理子系統,都是 通過可達性分析(Reachability Analysis)算法來判定對象是否存活的。這個算法的基本思路就是通過 一系列稱為“GC Roots”的根對象作為起始節點集,從這些節點開始,根據引用關系向下搜索,搜索過 程所走過的路徑稱為“引用鏈”(Reference Chain),如果某個對象到GC Roots間沒有任何引用鏈相連, 或者用圖論的話來說就是從GC Roots到這個對象不可達時,則證明此對象是不可能再被使用的。

如圖所示,對象object 5、object 6、object 7雖然互有關聯,但是它們到GC Roots是不可達的, 因此它們將會被判定為可回收的對象。


在Java技術體系里面,固定可作為GC Roots的對象包括以下幾種:

  • 在虛擬機棧(棧幀中的本地變量表)中引用的對象,譬如各個線程被調用的方法堆棧中使用到的 參數、局部變量、臨時變量等。
  • 在方法區中類靜態屬性引用的對象,譬如Java類的引用類型靜態變量。
  • 在方法區中常量引用的對象,譬如字符串常量池(String Table)里的引用。
  • 在本地方法棧中JNI(即通常所說的Native方法)引用的對象。
  • Java虛擬機內部的引用,如基本數據類型對應的Class對象,一些常駐的異常對象(比如 NullPointExcepiton、OutOfMemoryError)等,還有系統類加載器。
  • 所有被同步鎖(synchronized關鍵字)持有的對象。
  • 反映Java虛擬機內部情況的JMXBean、JVMTI中注冊的回調、本地代碼緩存等。
  • 除了這些固定的GC Roots集合以外,根據用戶所選用的垃圾收集器以及當前回收的內存區域不 同,還可以有其他對象“臨時性”地加入,共同構成完整GC Roots集合。譬如后文將會提到的分代收集 和局部回收(Partial GC),如果只針對Java堆中某一塊區域發起垃圾收集時(如最典型的只針對新生 代的垃圾收集),必須考慮到內存區域是虛擬機自己的實現細節(在用戶視角里任何內存區域都是不 可見的),更不是孤立封閉的,所以某個區域里的對象完全有可能被位于堆中其他區域的對象所引 用,這時候就需要將這些關聯區域的對象也一并加入GC Roots集合中去,才能保證可達性分析的正確 性。

目前最新的幾款垃圾收集器無一例外都具備了局部回收的特征。如OpenJDK中的G1、Shenandoah、ZGC以及Azul的PGC、C4這些收集器。

3、四種引用

無論是通過引用計數算法判斷對象的引用數量,還是通過可達性分析算法判斷對象是否引用鏈可 達,判定對象是否存活都和“引用”離不開關系。在JDK 1.2版之前,Java里面的引用是很傳統的定義: 如果reference類型的數據中存儲的數值代表的是另外一塊內存的起始地址,就稱該reference數據是代表 某塊內存、某個對象的引用。一個對象在 這種定義下只有“被引用”或者“未被引用”兩種狀態,對于描述一些“食之無味,棄之可惜”的對象就顯 得無能為力。譬如我們希望能描述一類對象:當內存空間還足夠時,能保留在內存之中,如果內存空 間在進行垃圾收集后仍然非常緊張,那就可以拋棄這些對象——很多系統的緩存功能都符合這樣的應 用場景。

在JDK 1.2版之后,Java對引用的概念進行了擴充,將引用分為強引用(Strongly Re-ference)、軟 引用(Soft Reference)、弱引用(Weak Reference)和虛引用(Phantom Reference)4種,這4種引用強 度依次逐漸減弱。

  • 強引用是最傳統的“引用”的定義,是指在程序代碼之中普遍存在的引用賦值,即類似“Object obj=new Object()”這種引用關系。無論任何情況下,只要強引用關系還存在,垃圾收集器就永遠不會回 收掉被引用的對象。
  • 軟引用是用來描述一些還有用,但非必須的對象。只被軟引用關聯著的對象,在系統將要發生內 存溢出異常前,會把這些對象列進回收范圍之中進行第二次回收,如果這次回收還沒有足夠的內存, 才會拋出內存溢出異常。在JDK 1.2版之后提供了SoftReference類來實現軟引用。
  • 弱引用也是用來描述那些非必須對象,但是它的強度比軟引用更弱一些,被弱引用關聯的對象只 能生存到下一次垃圾收集發生為止。當垃圾收集器開始工作,無論當前內存是否足夠,都會回收掉只 被弱引用關聯的對象。在JDK 1.2版之后提供了WeakReference類來實現弱引用。
  • 虛引用也稱為“幽靈引用”或者“幻影引用”,它是最弱的一種引用關系。一個對象是否有虛引用的 存在,完全不會對其生存時間構成影響,也無法通過虛引用來取得一個對象實例。為一個對象設置虛 引用關聯的唯一目的只是為了能在這個對象被收集器回收時收到一個系統通知。在JDK 1.2版之后提供 了PhantomReference類來實現虛引用。

4、生存還是死亡?

即使在可達性分析算法中判定為不可達的對象,也不是“非死不可”的,這時候它們暫時還處于“緩 刑”階段,要真正宣告一個對象死亡,至少要經歷兩次標記過程:如果對象在進行可達性分析后發現沒 有與GC Roots相連接的引用鏈,那它將會被第一次標記,隨后進行一次篩選,篩選的條件是此對象是 否有必要執行finalize()方法。假如對象沒有覆蓋finalize()方法,或者finalize()方法已經被虛擬機調用 過,那么虛擬機將這兩種情況都視為“沒有必要執行”。

finalize()方法是Object的一個方法,所有的對象都是Object的子類。只不過語法上默認沒有繼承Object。但是實際上是繼承的Object類。

如果這個對象被判定為確有必要執行finalize()方法,那么該對象將會被放置在一個名為F-Queue的 隊列之中,并在稍后由一條由虛擬機自動建立的、低調度優先級的Finalizer線程去執行它們的finalize() 方法。這里所說的“執行”是指虛擬機會觸發這個方法開始運行,但并不承諾一定會等待它運行結束。

這樣做的原因是,如果某個對象的finalize()方法執行緩慢,或者更極端地發生了死循環,將很可能導 致F-Queue隊列中的其他對象永久處于等待。

如果對 象要在finalize()中成功拯救自己 只要重新與引用鏈上的任何一個對象建立關聯即可,譬如把自己 (this關鍵字)賦值給某個類變量或者對象的成員變量,那在第二次標記時它將被移出“即將回收”的集 合;如果對象這時候還沒有逃脫,那基本上它就真的要被回收了。從下面代碼我們可以看到一個 對象的finalize()被執行,但是它仍然可以存活。

一次對象自我拯救的演示:

此代碼演示了兩點:

  • 對象可以在被GC時自我拯救。
  • 這種自救的機會只有一次,因為一個對象的finalize()方法最多只會被系統自動調用一次
  • public class FinalizeEscapeGC {public static FinalizeEscapeGC SAVE_HOOK = null;public void isAlive() {System.out.println("yes, i am still alive :)");}@Overrideprotected void finalize() throws Throwable {super.finalize();System.out.println("finalize method executed!");FinalizeEscapeGC.SAVE_HOOK = this;}public static void main(String[] args) throws Throwable {SAVE_HOOK = new FinalizeEscapeGC();//對象第一次成功拯救自己SAVE_HOOK = null;System.gc();// 因為Finalizer方法優先級很低,暫停0.5秒,以等待它Thread.sleep(500);if (SAVE_HOOK != null) {SAVE_HOOK.isAlive();} else {System.out.println("no, i am dead :(");}// 下面這段代碼與上面的完全相同,但是這次自救卻失敗了SAVE_HOOK = null;System.gc();// 因為Finalizer方法優先級很低,暫停0.5秒,以等待它Thread.sleep(500);if (SAVE_HOOK != null) {SAVE_HOOK.isAlive();} else {System.out.println("no, i am dead :(");}} }

    運行結果:

    finalize()方法大家盡量避免使用它,它的運行代價高昂,不確定性大,無法保證各個對象的調用順序,如今已被官方明確聲明為 不推薦使用的語法。finalize()能做的所有工作,使用try-finally或者其他方式都可以做得更好、 更及時。

    5、回收方法區

    方法區的垃圾收集主要回收兩部分內容:廢棄的常量和不再使用的類型?;厥諒U棄常量與回收 Java堆中的對象非常類似。舉個常量池中字面量回收的例子,假如一個字符串“java”曾經進入常量池 中,但是當前系統又沒有任何一個字符串對象的值是“java”,換句話說,已經沒有任何字符串對象引用 常量池中的“java”常量,且虛擬機中也沒有其他地方引用這個字面量。如果在這時發生內存回收,這個“java”常量就將會被系統清理出常量池。常量池中其他類(接 口)、方法、字段的符號引用也與此類似。

    判定一個常量是否“廢棄”還是相對簡單,而要判定一個類型是否屬于“不再被使用的類”的條件就 比較苛刻了。需要同時滿足下面三個條件:

    • 該類所有的實例都已經被回收,也就是Java堆中不存在該類及其任何派生子類的實例。
    • 加載該類的類加載器已經被回收,這個條件除非是經過精心設計的可替換類加載器的場景,如 OSGi、JSP的重加載等,否則通常是很難達成的。
    • 該類對應的java.lang.Class對象沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方 法。

    Java虛擬機被允許對滿足上述三個條件的無用類進行回收,這里說的僅僅是“被允許”,而并不是 和對象一樣,沒有引用了就必然會回收。關于是否要對類型進行回收,HotSpot虛擬機提供了命令參數進行控制,

    -Xnoclassgc :關閉虛擬機對class的垃圾回收功能。
    -verbose:class XXX :(XXX為程序名)你會在控制臺看到加載的類的情況。
    -XX:+TraceClassLoading :監控類的加載
    -XX:+TraceClassUnLoading : 監控類的卸載

    其中-verbose:class和-XX:+TraceClassLoading可以在 Product版的虛擬機中使用,-XX:+TraceClassUnLoading參數需要FastDebug版的虛擬機支持。

    在大量使用反射、動態代理、CGLib等字節碼框架,動態生成JSP以及OSGi這類頻繁自定義類加載 器的場景中,通常都需要Java虛擬機具備類型卸載的能力,以保證不會對方法區造成過大的內存壓 力。

    三、垃圾收集算法

    Java默認的虛擬機HotSpot VM,采用的追蹤式垃圾收集,也就是剛剛所說的可達分析,所以本節介紹的所有算法均屬于追蹤式垃圾收集的范疇。

    1、分代收集理論

    當前商業虛擬機的垃圾收集器,大多數都遵循了“分代收集”的理論進 行設計,分代收集名為理論,實質是一套符合大多數程序運行實際情況的經驗法則,它建立在兩個分代假說之上:

    1)弱分代假說(Weak Generational Hypothesis):絕大多數對象都是朝生夕滅的。
    2)強分代假說(Strong Generational Hypothesis):熬過越多次垃圾收集過程的對象就越難以消 亡

    這兩個分代假說共同奠定了多款常用的垃圾收集器的一致的設計原則:收集器應該將Java堆劃分 出不同的區域,然后將回收對象依據其年齡(年齡即對象熬過垃圾收集過程的次數)分配到不同的區 域之中存儲。

    在Java堆劃分出不同的區域之后,垃圾收集器才可以每次只回收其中某一個或者某些部分的區域 ——因而才有了“Minor GC”“Major GC”“Full GC”這樣的回收類型的劃分;發展出了“標記-復制算法”“標記-清除算 法”“標記-整理算法”等針對性的垃圾收集算法,這一切的出現都始于分代收集理論。

    把分代收集理論具體放到現在的商用Java虛擬機里,設計者一般至少會把Java堆劃分為新生代 (Young Generation)和老年代(Old Generation)兩個區域。顧名思義,在新生代中,每次垃圾收集 時都發現有大批對象死去,而每次回收后存活的少量對象,將會逐步晉升到老年代中存放。分代收集并非只是簡單劃分一下內存區域那么容易,它至少存在一個明顯的困難:對象不 是孤立的,對象之間會存在跨代引用。

    假如要現在進行一次只局限于新生代區域內的收集(Minor GC),但新生代中的對象是完全有可 能被老年代所引用的,為了找出該區域中的存活對象,不得不在固定的GC Roots之外,再額外遍歷整 個老年代中所有對象來確保可達性分析結果的正確性,反過來也是一樣。遍歷整個老年代所有對象 的方案雖然理論上可行,但無疑會為內存回收帶來很大的性能負擔。為了解決這個問題,就需要對分 代收集理論添加第三條經驗法則:

    3)跨代引用假說(Intergenerational Reference Hypothesis):跨代引用相對于同代引用來說僅占極 少數。

    依據這條假說,我們就不應再為了少量的跨代引用去掃描整個老年代,也不必浪費空間專門記錄 每一個對象是否存在及存在哪些跨代引用,只需在新生代上建立一個全局的數據結構(該結構被稱 為“記憶集”,Remembered Set),這個結構把老年代劃分成若干小塊,標識出老年代的哪一塊內存會 存在跨代引用。此后當發生Minor GC時,只有包含了跨代引用的小塊內存里的對象才會被加入到GC Roots進行掃描。雖然這種方法需要在對象改變引用關系(如將自己或者某個屬性賦值)時維護記錄數 據的正確性,會增加一些運行時的開銷,但比起收集時掃描整個老年代來說仍然是劃算的。

    2、名詞解釋

    部分收集(Partial GC):指目標不是完整收集整個Java堆的垃圾收集,其中又分為:

    • 新生代收集(Minor GC/Young GC):指目標只是新生代的垃圾收集。
    • 老年代收集(Major GC/Old GC):指目標只是老年代的垃圾收集。目前只有CMS收集器會有單 獨收集老年代的行為。另外請注意“Major GC”這個說法現在有點混淆,在不同資料上常有不同所指,有的是指老年代的收集、有的是指整堆收集。
    • 混合收集(Mixed GC):指目標是收集整個新生代以及部分老年代的垃圾收集。目前只有G1收 集器會有這種行為。
    • 整堆收集(Full GC):收集整個Java堆和方法區的垃圾收集。

    通常能單獨發生收集行為的只是新生代,所以這里“反過來”的情況只是理論上允許,實際上除了 CMS收集器,其他都不存在只針對老年代的收集。

    3、標記-清除算法

    算法分為“標記”和“清除”兩個階段:首先標記出需要回 收的對象,在標記完成后,統一回收掉所有被標記的對象,也可以反過來,標記存活的對象,統一回 收所有未被標記的對象。標記過程就是對象是否屬于垃圾的判定過程,怎么判斷是否垃圾,就是剛剛所提到的,引用計數算法和可達分析算法。

    它的主要缺點有兩個:第一個是執行效率不穩定,如果Java堆中包含大量對 象,而且其中大部分是需要被回收的,這時必須進行大量標記和清除的動作,導致標記和清除兩個過 程的執行效率都隨對象數量增長而降低;第二個是內存空間的碎片化問題,標記、清除之后會產生大 量不連續的內存碎片,空間碎片太多可能會導致當以后在程序運行過程中需要分配較大對象時無法找 到足夠的連續內存而不得不提前觸發另一次垃圾收集動作。標記-清除算法的執行過程如圖所示。

    4、標記-復制算法

    標記-復制算法常被簡稱為復制算法。為了 解決標記-清除算法面對大量可回收對象時執行效率低 的問題,1969年Fenichel提出了一種稱為“半區復制”(Semispace Copying)的垃圾收集算法,它將可用 內存按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當這一塊的內存用完了,就將還存活著 的對象復制到另外一塊上面,復制的時候不用考慮有 空間碎片的復雜情況,只要移動堆頂指針,按順序分配即可,然后再把已使用過的內存空間一次清理掉。

    缺點:

  • 如果內存中多數對象都是存 活的,這種算法將會產生大量的內存間復制的開銷。
  • 回收算法的代價是將可用內存縮小為了原來的一半,空間浪費未免太多了一 點。
  • 在1989年,Andrew Appel針對具備“朝生夕滅”特點的對象,提出了一種更優化的半區復制分代策 略,現在稱為“Appel式回收”。HotSpot虛擬機的Serial、ParNew等新生代收集器均采用了這種策略來設 計新生代的內存布局。

    Appel式回收的具體做法是把新生代分為一塊較大的Eden空間和兩塊較小的 Survivor空間,每次分配內存只使用Eden和其中一塊Survivor。發生垃圾搜集時,將Eden和Survivor中仍 然存活的對象一次性復制到另外一塊Survivor空間上,然后直接清理掉Eden和已用過的那塊Survivor空間。HotSpot虛擬機默認Eden和Survivor的大小比例是8∶1,也即每次新生代中可用內存空間為整個新 生代容量的90%(Eden的80%加上一個Survivor的10%),只有一個Survivor空間,即10%的新生代是會 被“浪費”的。當然,98%的對象可被回收僅僅是“普通場景”下測得的數據,任何人都沒有辦法百分百 保證每次回收都只有不多于10%的對象存活,因此Appel式回收還有一個充當罕見情況的“逃生門”的安 全設計,當Survivor空間不足以容納一次Minor GC之后存活的對象時,就需要依賴其他內存區域(實 際上大多就是老年代)進行分配擔保。

    內存的分配擔保好比我們去銀行借款,如果我們信譽很好,在98%的情況下都能按時償還,于是 銀行可能會默認我們下一次也能按時按量地償還貸款,只需要有一個擔保人能保證如果我不能還款 時,可以從他的賬戶扣錢,那銀行就認為沒有什么風險了。內存的分配擔保也一樣,如果另外一塊 Survivor空間沒有足夠空間存放上一次新生代收集下來的存活對象,這些對象便將通過分配擔保機制直 接進入老年代,這對虛擬機來說就是安全的。

    5、標記-整理算法

    針對老年代對象的存亡特征,1974年Edward Lueders提出了另外一種有針對性的“標記-整 理”(Mark-Compact)算法,其中的標記過程仍然與“標記-清除”算法一樣,但后續步驟不是直接對可 回收對象進行清理,而是讓所有存活的對象都向內存空間一端移動,然后直接清理掉邊界以外的內 存,“標記-整理”算法的示意圖如圖所示。

    標記-清除算法與標記-整理算法的本質差異在于前者是一種非移動式的回收算法,而后者是移動 式的。是否移動回收后的存活對象是一項優缺點并存的風險決策:

    移動則內存回收時會更復雜,不移動則內存分配時會 更復雜。移動雖然復雜點,但是不影響服務器的內存吞吐量。HotSpot虛擬機里面關注吞吐量的Parallel Scavenge收集器是基于標記-整理算法的。

    還有一種“和稀泥式”解決方案可以不在內存分配和訪問上增加太大額外負擔,做法是讓虛 擬機平時多數時間都采用標記-清除算法,暫時容忍內存碎片的存在,直到內存空間的碎片化程度已經 大到影響對象分配時,再采用標記-整理算法收集一次,以獲得規整的內存空間。前面提到的基于標 記-清除算法的CMS收集器面臨空間碎片過多時采用的就是這種處理辦法(CMS是老年代垃圾收集器)。

    總結

    以上是生活随笔為你收集整理的(六)Java垃圾回收机制(附带代码示例)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。