Java&Android 基礎知識梳理(4) 垃圾收集器與內存分配策略

1、概述

GC須要考慮的三個問題:java

  • 哪些內存須要回收
  • 何時回收
  • 如何回收

在分析內存區域的時候,咱們把Java運行時數據區分爲兩個部分:算法

  • 程序計數器、虛擬機棧、本地方法棧:每一個棧幀中分配多少內存在類結構肯定下來就已知,所以這些區域的內存分配和回收具有肯定性,方法結束或線程結束時,內存就跟着被回收了。
  • Java堆、方法區:因爲一個接口中的多個實現類須要的內存可能不同,一個方法中的多個分支須要的內存也不同,只有在程序處於運行期間才能知道會建立哪些對象,所以這些區域的內存分配和回收是動態的。

2、如何判斷哪些是「存活」的實例

2.1 引用的分類

引用的定義:若是reference類型的數據中存儲的數值表明的另一塊內存的起始地址,就稱這塊內存表明引用。 引用的分類:數組

  • 強引用(Object a = new Object()):只要強引用存在,垃圾回收器永遠不會回收掉被引用的對象。
  • 軟引用(SoftReference):有用但並不是必須,在系統將要發生OOM異常以前,將會把這些對象列進回收範圍中進行第二次回收。
  • 弱引用(WeakReference):非必須對象,被弱引用的對象只能生存到下一次垃圾收集發生前。
  • 虛引用(PhantomReference):不會對生存時間產生影響,也沒法經過虛引用來取得一個對象實例,設置虛引用的惟一目的就是能在這個對象被垃圾回收器回收時收到一個系統通知。

2.2 引用計數法

給對象添加一個引用計數器,當有一個地方引用它時就加一,引用失效時就減一,當計數器的值爲零時表示它不可用。 可是它沒法解決相互循環引用問題安全

2.3 可達性分析

經過一系列的稱爲GC Roots的對象做爲起始點,從這些節點開始向下搜索,所走過的路徑稱爲引用鏈。當一個對象到GC Roots沒有任何引用鏈時,表示這個對象不可用,GC Roots的類型有:線程

  • 虛擬機棧中的局部變量表中引用的對象。
  • 方法區中類靜態屬性引用的對象。
  • 方法區中常量引用的對象。
  • 本地方法棧中**JNI**引用的對象。

2.4 finalize方法對於內存回收的影響

當某個對象在通過可達性分析後,發現它到GC Roots沒有任何引用鏈時,那麼它會被第一次標記,並進行第一次篩選,篩選的結果有兩種狀況:code

  • 沒有覆蓋finalize()方法或者虛擬機已經調用過它的finalize()方法:直接回收。
  • 其它狀況:把這個對象放置在一個F-Queue的隊列中,並在稍後由一個由虛擬機自動創建的、低優先級的Finalizer線程去執行這個對象的finalize()方法,如對象要在finalize方法中拯救本身,只要從新與引用鏈的某個變量關聯便可,那麼在第二次標記時它將被移出「即將回收」的集合,不然它將被回收。

這種方法代價高昂,不肯定性大,沒法保證各個對象的調用順序,所以能夠忘記這個方法的存在。對象

3、方法區的回收

對於方法區(HotSpot中的永久代)主要回收兩部份內容:廢棄常量和無用的類。接口

  • 廢棄常量 以常量池中字面量的回收爲例,若是一個字符串abc被放入了常量池中,可是沒有任何一個String對象引用它,那麼就會被清理出常量池,常量池中其它類(接口)、方法、字段的符號引用也相似。
  • 類 同時知足三個條件:
  • 該類的全部實例已經被回收
  • 加載該類的ClassLoader已經被回收
  • 該類對應的java.lang.Class對象沒有在任何地方被引用,沒法在任何地方經過發射訪問該類的方法。

4、垃圾收集算法基礎

4.1 標記 - 清除算法

  • 概念 首先標記出全部須要回收的對象,在標記完成後統一進行回收。
  • 缺點:
  • 標記和清除兩個過程效率不高。
  • 產生內存碎片,致使須要分配較大對象時,沒法找到足夠的連續內存而須要觸發一次GC操做。

4.2 複製算法

  • 概念 將可用內存劃分爲大小相等的兩塊,每次只使用其中的一塊,當一塊內存用完了。則觸發一次GC操做,將活着的對象複製到另外一塊上,而後再把已使用的內存空間一次清理掉。隊列

  • 缺點 將內存縮小爲了原來的一半。內存

  • 如今商業虛擬機採用這種算法的改良版來實現新生代的回收 它把內存按8:1:1分爲Eden/survivor0/survivor1三塊: 須要分配內存時,首先嚐試在Eden區分配,若是Eden區沒法分配,那麼嘗試把活着的對象放到survivor0中去:

  • 若是survivor0能夠放入,那麼放入以後清除Eden區。

  • 若是survivor0不能夠放入,那麼嘗試把Edensurvivor0的存活對象放到survivor1中:

    • 若是survivor1能夠放入,那麼放入survivor1以後清除Edensurvivor0,以後再把survivor1中的對象複製到survivor0中,保持survivor1一直爲空。
    • 若是survivor1不能夠放入,那麼直接把它們放入到老年代中,並清除Edensurvivor0,這個過程也稱爲分配擔保
  • 適用狀況 因爲複製算法在對象成活率較高時,須要較多的複製操做,效率會變低,因此在老年代中不能採用該算法。

4.3 標記 - 整理算法

  • 概念 和標記 - 清除算法相似,但後續步驟不是直接對可回收對象進行清理,而是讓全部存活的對象都向一端移動,而後直接清除掉端邊界之外的內存。
  • 優勢 解決了標記- 清除算法致使的內存碎片問題在存活率較高時複製算法效率低的問題

4.4 分代收集算法

當前商業虛擬機採用的方式,根據對象存活週期的不一樣將內存劃分爲幾塊,通常是新生代和老年代:

  • 新生代:每次垃圾收集時只有少許存活,選用複製算法的改良版,也就是上面說到的Eden/survivor0/survivor1的分配方式。
  • 老年代:對象存活率較高,且沒有分配擔保,必須用標記 - 清除或標記 - 整理算法來實現。

5、Minor GCMajor GC/Full GC

  • Minor GC:發生在新生代的垃圾回收動做,很是頻繁,回收速度也較快,採用的垃圾收集器有SerialParNewParallel Scavenge
  • Major GC/Full GC:發生在老年代的GC,常常伴隨至少一次的Minor GCMajor GC的速度通常會比Minor GC慢十倍以上,採用的垃圾收集器有CMSSerial OldParallel Old

6、對象分配的原則

  • 對象優先在Eden區分配 當Eden區沒有足夠空間,觸發一次Minor GC

  • 大對象直接進入老年代 例如很長的字符串以及數組,常常出現大對象容易致使內存還有很多空間時就提早觸發垃圾收集以獲取足夠的連續空間來「安置」它們。

  • 長期存活的對象將進入老年代 若是Eden區出生並進過第一次Minor GC後,仍然存活,而且被成功複製到survivor區中,那麼對象年齡變爲一,當對象在survivor中每熬過一次Minor GC,年齡就增長一,當年齡增長到必定程度,就會晉升到老年代中。

  • 動態對象年齡綁定 若是survivor空間中相同年齡全部對象大小的總和大於survivor空間的一半,年齡大於或等於該年齡的對象就能夠進入老年代,無須到達要求的年齡。

  • 空間分配擔保 在發生Minor GC前,檢查老年代最大可用連續空間是否大於新生代全部對象總空間:

  • 大於,那麼操做是安全的,不對老年代進行Full GC

  • 小於,檢查HandlePromotionFailure設置值是否容許失敗:

    • 容許:檢查老年代最大可用連續空間是否大於歷次晉升到老年代對象的平均大小:
      • 大於:不對老年代進行Full GC。在這以後,由於有可能出現某次存活對象激增的狀況,這種屬於冒險行爲,若是出現了擔保失敗(也就是Edensurvivor0的存活對象既沒法放入survivor1,也沒法放入老年代的連續空間中),那麼會在失敗以後對老年代進行Full GC
      • 小於:先對老年代進行一次Full GC
    • 不容許:先對老年代執行一次Full GC
相關文章
相關標籤/搜索