每日一問:講講 Java 虛擬機的垃圾回收

昨天咱們用比較精簡的文字講了 Java 虛擬機結構,沒看過的能夠直接從這裏查看: 每日一問:你瞭解 Java 虛擬機結構麼?算法

今天咱們必須來看看 Java 虛擬機的垃圾回收算法是怎樣的。不過在開始以前,咱們必定得肯定哪些是活着的對象,又有哪些是能夠進行回收的。緩存

判斷對象是否存活方式

引用計數算法

對應判斷一個對象是否能夠回收,我想引用計數必定是最容易被想到的算法了吧。給每一個對象加一個引用計數器,每當有一個地方引用它時,計數器就加 1,引用失效後減 1,當對象的計數器爲 0,則說明這個對象能夠被回收了。這個算法很是簡單,但存在一個很是大的弊端:一旦兩個對象相互引用,這個算法就沒轍了。網絡

根搜索算法

Java 就是採用的根搜索算法進行判斷對象是否存活。這個算法的思路是:經過一系列名爲 "GC Roots" 的對象做爲起始點,從這些結點開始向下搜索,當一個對象到 "GC Roots" 沒有任何引用鏈相連的話,則證實這個對象是能夠被回收的。在 Java 中,能夠做爲 "GC Roots" 的對象包括:學習

  • 虛擬機棧中引用的對象;
  • 方法區中的類靜態屬性和常量引用的對象;
  • 本地方法棧中 JNI 引用的對象;

四種引用

在 JDK 1.2 以後,引用被分爲了強引用、軟引用、弱引用和虛引用四種,這四種引用強度依次逐漸減弱。code

強引用

強引用在 Android 代碼中廣泛存在,只要強引用還在,垃圾回收器就不會回收掉被引用的對象,這就是爲何咱們用內部類持有 Activity 實例會形成內存泄漏的根本緣由。對象

軟引用

軟引用用來描述一些還有用,但非必需的對象,用 SoftReference 實現,**被軟引用關聯的對象,在系統將要發生 OOM 以前,會把這些對象列進回收範圍之中並進行第二次回收。**軟引用在 Android 中主要是用於作緩存,好比軟引用緩存網絡請求的圖片。生命週期

弱引用

弱引用也是用來描述非必需對象的,但它的強度比軟引用更弱,用 WeakReference 實現。**被弱引用管理的對象只能生存到下一次垃圾收集發生以前。**弱引用在 Android 中主要用於處理內存泄漏。圖片

虛引用

虛引用其實沒啥好說的,一個對象是否有虛引用的存在,徹底不會對生存時間構成影響,也沒法經過虛引用來取得一個對象實例。就目前爲止,我尚未在 Android 開發中使用過它。內存

都有些什麼垃圾回收算法

學習 Java 虛擬機的垃圾回收算法以前,咱們必須來看看咱們常見的幾種垃圾回收算法的思想,並把它們的優劣進行必定的對比,這樣必定才能讓你理解更加深入。開發

標記 - 清除算法

標記 - 清除算法應該是最簡單基礎的收集算法了,只須要標記須要回收的對象,標記完成後統一回收便可。但其有兩個很是明顯的弊端。

  • 標記清除效率都不高;
  • 標記清除後會產生大量不連續的內存碎片,致使程序之後須要較大對象時沒法找到足夠的連續內存而不得不提早觸發另外一次垃圾收集動做。
複製收集算法

複製算法主要是將可用內存劃分爲大小相等的兩塊,每次只使用其中的一塊,當這一塊內存用完,就將存活着的對象複製到另外一塊內存上去,而後把已使用過的內存空間一次性清理掉。複製回收算法能有效地避免內存碎片,可是算法須要把內存一分爲二,致使內存使用率大大下降。

標記 - 整理算法

複製收集算法在對象存活率較高時就須要進行較多的複製操做,效率非很低。 效率會很低。標記-整理算法就解決了這樣的問題,一樣採用的是根搜索算法進行存活對象標記,但後續是將全部存活的對象都移動到內存的一端,而後清理掉端外界的對象。

分代收集算法

當前包括 Java 虛擬機在內的商業虛擬機都採用的是分代收集算法。這種算法其實就是根據對象的存活週期不一樣將內存劃分爲幾塊。通常把 Java 堆分爲新生代和老年代,而後根據各個年代的特色採用最適合的收集算法。

Java 虛擬機的垃圾回收策略

前面說了 Java 虛擬機採用的是分代回收算法,該算法會根據各個年代的特色採用最適合的收集算法,咱們就必須瞭解 Java 堆分的各個年代區域的特色。

JVM 中共分爲三個代:新生代、老年代和持久代。其中持久代主要存放的是 Java 類的類信息,與垃圾收集要收集的 Java 對象關係不大。

  • 新生代:全部新生成的對象首先都是放在新生代的,新生代採用複製回收算法。新生代的目標就是儘量快速地收集掉那些生命週期短的對象。新生代按照 8:1 的比例分爲一個 Eden 區和兩個 Survivor 區。大部分對象在 Eden 區生成,當 Eden 區滿時,還存活的對象將被複制到其中的一個 Survivor 區,當這個 Survivor 區滿時,此區的存活對象將被複制到另一個 Survivor 區,當另外一個 Survivor 區也滿了的時候,從第一個 Survivor 區複製過來的而且此時還存活的對象,將被複制到了「年老區 」。須要注意,Survivor 的兩個區是對稱的,沒有任何的前後關係,因此同一個區中可能同時存在 Eden 複製過來的對象,和從前一個 Survivor 區複製過來的對象,而複製到年老區的只有從第一個 Survivor 區過來的對象,並且,Survivor 區總有一個是空的。
  • 老年代:在新生代中經歷了 N 次垃圾回收後仍然存活的對象,就會被放到老年代中,老年代採用標記整理回收算法。所以,能夠認爲老年代中存放的都是一些生命週期較長的對象。
  • 持久代:用於存放靜態文件,如 final 常量、static 常量、常量池等。持久代對垃圾回收沒有顯著影響,但有些應用可能動態生成或者調用一些 class。在這種時候須要設置一個比較大的持久代空間來存放這些運行過程當中新增的類。

談談 Java 垃圾回收的觸發條件

Java 垃圾回收包含兩種類型:Scavenge GC 和 Full GC。

  • Scavenge GC:通常狀況下,當新對象生成,而且在 Eden 申請空間失敗的時候,就會觸發 Scavenge GC,對 Eden 區進行 GC,清除非存活的對象,而且把尚且存活的對象移動到 Survivor 區,而後整理 Survivor 的兩個區。這種方式的 GC 是對新生代的 Eden 區進行,不會影響到老年代。由於大部分對象都是從 Eden 區開始的,同時 Eden 區不會分配的很大,因此 Eden 區的 GC 會頻繁進行。
  • Full GC:Full GC 將會對整個堆進行整理,包括新生代、老年代和持久代。Full GC 由於須要對整個堆進行回收,因此比 Scavenge GC 要慢,所以應該儘可能減小 Full GC 的次數。在對 JVM 調優的過程當中,很大一部分工做就是對 Full GC 的調節,有以下緣由可能致使 Full GC:
    1. 老年代被寫滿;
    2. 持久代被寫滿;
    3. System.gc() 被顯式調用;

好了,這一篇文字比起前面的文字稍微多了一些,主要是知識關聯性稍微大了一些,又不適合分開講解,因此就只能這樣了。

相關文章
相關標籤/搜索