No.4 Java的記憶體回收(記憶體回收)

來源:互聯網
上載者:User

標籤:ref   --   過程   強引用   使用   標識   ...   shm   對象   

1. Java引用的種類

 記憶體管理分為:記憶體配置和記憶體回收。都是由JVM自動處理的

  • 對象在記憶體中的狀態:可達、可恢複(回收前調用finalize方法)、不可達
    • JVM回收標準:是否還有引用變數引用該對象
    • 有向圖理解。線程對象作為根節點,變數、對象作為節點,參考關聯性作為有向邊。在有向圖中,從線程節點<當然線程對象也要存在,沒有被銷毀>可達的對象都是可達狀態。
  • 強引用
    • 一般的引用/大部分 都是強引用,被強引用的對象不會被回收,是記憶體泄露的主要原因之一。
  • 軟引用
    • 通過SoftReference類實現(該類的用法)
    • 只有軟引用的對象,當系統記憶體充足時不會被回收,當記憶體緊張時會被回收
    • 可以用來解決系統記憶體緊張的難題
  • 弱引用
    • 字串常量會被系統緩衝(會使用強引用來引用),系統不會回收被緩衝的字串常量。
    • 當記憶體回收機制運行(具有不確定性)時,不管系統記憶體是否緊張,都會被回收(不確定性)。
    • WeakReference類,WeakHashMap更常用
  • 虛引用
    • 主要用於跟蹤對象被記憶體回收的狀態:程式可以通過檢查與虛引用關聯的引用隊列中是否已經包含指定的虛引用,從而瞭解虛引用所引用的對象是否即將被回收。
    • 不能單獨使用,需要和引用隊列聯合使用
      • 軟引用和弱引用與引用隊列聯合使用,系統回收被引用對象之後,將會把被回收對象的對應的引用添加到關聯的引用隊列中去。
      • 而虛引用與引用隊列聯合使用的時候,在對象被釋放之前,將把引用它的虛引用添加到引用隊列中去,這使得可以在對象被回收之前採取行動。
    • 無法通過虛引用擷取它所引用的對象

2. Java的記憶體泄露

  • 不在使用的記憶體卻沒有被回收,就是記憶體泄露
  • Java中,可達狀態但是不再使用的記憶體,會引起記憶體泄露(記憶體回收機制不會回收可達狀態的記憶體)
  • eg:ArrayList中的remove方法elementData[--size] = null;

3. 記憶體回收機制

  • 兩件事情:回收不可達對象;清理記憶體配置、回收過程中產生的記憶體片段(不連續的記憶體空間)
  • 記憶體回收的基本演算法
    • 串列回收和並行回收
    • 並發執行和應用程式停止
    • 壓縮、不壓縮、複製
      • 複製、標記清楚、標記壓縮
  • 堆記憶體的分代回收(堆記憶體被分為三個代來存放對象)
    • 依據:對象存留時間的長短,然後根據不同代採用不同的記憶體回收策略,充分發揮各自的優勢。
      • 基於如下兩點事實:①絕大多數對象不會被長時間引用,Young代就會被回收;②很老的對象和很新的對象之間很少存在相互引用的情況
    • Young代:複製演算法;可達狀態的對象較少,複製成本不大
      • 1個Eden區和2個Survivor區:對象先被分配到Eden區(一些大的對象可能直接分配到Old代),Survivor區中的對象至少經曆一次記憶體回收。一次複製演算法會將Eden區和1個Survivor區中的可達對象複製到另一個空的Survivor區中,然後進入下一個迴圈,即兩個Survivor區同時間有一個是用來儲存對象,另一個是空的
    • Old代
      • Young代多次回收仍然存在的對象會進入到Old代中,其中對象的特點:不容易死;隨時間的流逝,其中的對象會越來越多,因此Old代的空間成本要比Young代大
      • 回收特點:記憶體回收的執行頻率無需太高,因為死去的對象較少;每次執行需要花費更多的時間完成
      • 標記壓縮演算法,不會大量地產生記憶體片段
    • Permanent代
      • 主要用於裝載Class、方法等資訊,記憶體回收機制通常不會回收該代中的對象
    • 次要回收:當Young代的記憶體將要用完的時候,記憶體回收機制會對該代進行記憶體回收,回收頻率較高,系統開銷小。
    • 主要回收:當Old代的記憶體將要用完的時候,記憶體回收機制會進行全回收,即對Young代和Old代都要進行回收,此時回收成本較大,因此成為主要回收
    • 通常來說,Young代先被回收,Old代的回收頻率要低的多;記憶體壓縮時,每個代都獨立的進行壓縮
  • 常見記憶體回收行程
    • 串列回收器
      • Young代:串列複製演算法
      • Old代:串列標記壓縮演算法,三個階段:mark、sweep、compact(壓縮階段,執行sliding compaction,將使用中的物件往Old代的前端移動,尾部保留連續的空間)
    • 並行回收器
      • Young:與串列回收器基本相似,增加多CPU並行的能力,即啟動了多個線程來並行回收(並不是與主線程並發,而是多個回收線程並存執行
      • Old:與串列完全相同
    • 並行壓縮回收器(將取代並行回收器)
      • Young:與並行完全相同
      • Old:將Old代分成幾個固定大小的地區
        • mark:多個回收線程並行標記可達對象(會更新可達對象所在地區的大小及其位置資訊)
        • summary:操作Old代地區(而不是單個對象)。從最左邊檢驗地區密度,當某地區的密度達到某個數值時,判定該地區及其右邊地區應該回收(進行壓縮及回收空間),其左邊地區標識為密集地區(不會將新對象移到這裡,也不會對該地區進行壓縮)。該階段:串列實現
        • compact:利用上階段產生的資料識別出需要裝填的地區,多個回收線程並行地將資料複製到該地區中。該階段後,Old代一端密集儲存大量的使用中的物件,另一端則為大塊的空閑塊。
    • 並發標識-清理 回收器(CMS),適用於即時性要求較高的程式,對Old代回收:並發執行,程式僅僅需要兩次很短的暫停
      • Young:與並行回收器完全相同,依然會導致應用程式暫停,
      • Old:並行作業
        • mark:
          • 記憶體回收開始時,短暫的暫停,標識直接引用的可達對象(initial mark);
          • 並發標識階段(concurrent marking phase),依據初始標識中發現的可達對象來尋找其他可達對象;
          • 再標記階段(remark),因為並發標識過程中 應用程式可能會重新產生可達對象,為避免漏掉這些,需要再次很短的暫停下,多線程並行地再標識
        • concurrent sweep:並發清理
      • 預設在Old代68%滿時就開始回收,因為是並行操作的,如果等到Old代滿了,應用程式就沒有可用記憶體了;而其他回收器回收時,不是並行操作的,在回收時,程式會暫停,應用程式不產生新的對象。
      • CMS不會對Old代進行記憶體壓縮,即它的可用空間是不連續的,儲存了一份可用空間列表,分配記憶體效率下降。
      • CMS需要更大的堆記憶體,因為:並發標識時,此時應用程式也在指派至,Old代會同時增長;在標識階段成為垃圾的對象並不能立即被回收,只有等到並行清理階段時才被回收。(mine:串列,mark之後執行sweep,之間程式暫停,不會再產生新的對象;並發,mark(含有並發mark)之後並發sweep,程式並發執行,仍然可能產生新的對象,待清理的對象和新產生的對象在一起自然要大一點的記憶體。)(總之,因為並行,可能在清理的時候又產生新的對象,所以需要更大點的記憶體;而串列時,清理的時候不會產生新的對象,清理完畢後才會產生新的對象,需要的記憶體沒有這麼大)
      • 可執行附加選項強制回收permanent代的記憶體
      • 串列回收和並發回收對比(mine)
        • 串列:應用程式暫停——>回收器執行回收階段(mark——>....——>sweep...)——>程式繼續執行
        • 並發:應用程式短暫暫停1——>CMS執行initial mark(標記直接引用的可達對象)——>程式執行,CMS並發標記concurrent mark(標記通過初始標識標識的可達對象能尋找到的其他可達對象)——>程式短暫暫停2——>remark(標記並發標記時程式新產生的可達對象)——>程式繼續執行,並發清理<此時才真正開始清理操作,之前都是標識操作>

4. 記憶體管理的小技巧

  • 盡量使用直接量
    • 使用字串以及Byte、Short、Interger...等封裝類時,直接使用直接量建立,而不用new 關鍵字new新的對象
  • 進行字串拼接時使用StringBuilder/StringBuffer,而不是String(String不可變,用其拼接時會產生大量的臨時字串)
  • 儘早釋放無用對象的引用
    • 一般情況下局部變數的作用時間比較段,對其引用值無需 = null;但是若是其所在的方法中執行了耗記憶體、費時的操作時,應該將對無用對象的引用變數的值賦值null,儘早釋放無用對象
  • 盡量少用靜態變數,尤其是靜態變數引用對象。因為靜態變數屬於Class類對象,類對象在permanent代中(它的類變數自然也在該代中),其存在時間很長
  • 避免在經常調用的方法、迴圈中建立對象。
    • 導致不斷的為對象分配(建立)、回收(銷毀)記憶體,這些操作都是影響效能的
  • 緩衝經常使用的對象(eg:資料庫連接池)
    • 避免不斷的分配、回收操作
    • 方法:
      • 使用HashMap:控制容器中的key-value對不能太多,否則HashMap佔用過大的記憶體將導致效能下降
      • 使用開源快取項目
    • 緩衝設計:
      • 犧牲空間來換取時間,都是使用容器儲存已用過的對象。如何控制容器佔用的記憶體,又保留大部分已用過的對象,是程式設計的關鍵。(一些緩衝演算法)
  • 盡量不使用finalize方法
    • 記憶體回收行程回收資源前會調用該方法。
    • 回收機制本身已經教嚴重製約應用程式的效能
    • 本身回收機制的負擔就比較重,而且回收Young代記憶體會導致程式暫停,影響效能;再在finalize方法中執行資源清理會加重回收機制的負擔,導致運行效率更差
  • 考慮使用SoftReference
    • 建立長度很大的數組時,考慮使用軟引用進行封裝數組元素(記憶體夠時引用,不足時釋放)
    • 軟引用具有不確定性,當擷取引用對象時,需要顯示判斷對象是否為空白(避免出現異常),若為空白,應該重新建立。
  • 吼吼吼

 

No.4 Java的記憶體回收(記憶體回收)

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.