【Android 內存優化】垃圾回收算法 ( 內存優化總結 | 常見的內存泄漏場景 | GC 算法 | 標記清除算法 | 複製算法 | 標記壓縮算法 )





一、 內存優化總結



內存泄漏原理 : 長生命週期對象 , 持有短生命週期對象的引用 , 並且是強引用持有 , GC 無法釋放該短生命週期對象引用 , 造成 OOM ;

Android Profiler 工具參考官方文檔 : 使用 Memory Profiler 查看 Java 堆和內存分配


使用 Memory Analyzer ( MAT ) 內存分析工具分析內存快照 , 首先要將內存快照文件 , 轉化成 MAT 工具能識別的文件 , 然後使用 MAT 工具進行分析 ;


在博客 【Android 內存優化】Android Profiler 工具常用功能 ( 監測內存 | 內存快照 ) 中保存了內存快照文件 memory-20200625T145636.hprof , 要使用 MAT 工具分析該內存快照 , 需要先將該文件轉換成爲 MAT 標準的文件格式 ;


在博客 【Android 內存優化】使用 Memory Analyzer ( MAT ) 工具分析內存 ( hprof 文件轉換 | MAT 工具下載 | MAT 工具使用 ) 中轉換了 MAT 格式的內存快照 , 下載 Memory Analyzer ( MAT ) 內存分析工具 , 並在該工具中加載了 MAT 格式的文件 ;


在博客 【Android 內存優化】使用 Memory Analyzer ( MAT ) 工具分析內存 ( MAT 工具使用 | 最大對象 | 類實例個數 | 引用與被引用 | GC Roots 最短鏈 )使用 Memory Analyzer ( MAT ) 內存分析工具 中分析內存快照 , 主要是查看 GC Roots 最短鏈 , 分析出在哪個類中引用了該對象 ;





二、 常見的內存泄漏場景



內存泄漏的常見原因 :

  • 集合的使用
  • 靜態成員
  • 常量
  • 單例模式 : 不要在單例中隨便持有 Context , Activity 之類的成員 , 有極大的內存泄漏隱患 ;
  • 沒有釋放或關閉的資源 : 如 IO 流 , Socket 等 ;
  • 線程 : 界面退出 , 線程沒有退出 , 線程持有的引用就泄漏了 ; 儘量在其中使用弱引用 ;
  • Handler : 非靜態內部類造成內存泄漏 ;




三、 內存回收算法



1. 內存抖動 : 應用對象的內存 , 頻繁的分配 , 回收 , 造成內存使用量上下抖動 , UI 卡頓 , 嚴重時甚至造成 OOM ( OutOfMemoryError ) , 造成內存溢出 ;


2. 內存抖動造成溢出原因 : 對象頻繁分配 , 回收 , 會大量造成內存的空隙 , 這些空隙很小無法分配大塊內存 , 當整個內存都是這種空隙時 , 無法爲大塊內存分配空間 , 就造成了 OOM 異常 ;


3. GC 垃圾回收之前 , 需要對內存對象進行採集 , 不同的虛擬機使用不同的垃圾回收算法 , 常用的垃圾回收算法 :

  • 標記-清除算法 ( mark-sweep )
  • 複製算法
  • 標記-壓縮算法
  • 分代收集算法




四、 標記-清除算法 ( mark-sweep )



標記-清除算法 ( mark-sweep ) : 步驟分爲兩步 : ① 標記 , ② 清除 ;


內存中分爲如下幾塊 :

  • 可回收對象
  • 存活對象
  • 可用內存

標記-清除算法 ( mark-sweep ) 算法中 , 首先標記出可回收對象 , 標記完成之後 , 統一回收 ;

回收完畢後 , 存活的對象仍然保持在原來的位置 , 可用內存基本支離破碎 , 這樣就會造成內存碎片 , 這些內存碎片中無法申請大塊內存 ;

在這裏插入圖片描述

上圖中的內存中 , 有 24 個格子的空閒內存 , 如果要申請 55 個單位格子的內存 , 發現無法申請 , 沒有連續 5 個格子的內存 , 此時直接出現 OOM ;

有很多內存, 但都是支離破碎的 , 沒有大塊內存 ;





五、 複製算法



1. 複製算法 : 將可用內存 , 分爲兩個想等於內存區域塊 , 區域 11 區域 22 , 使用時只使用其中的一個區域 ;

  • 垃圾回收前 , 只使用區域 11 的內存
  • 垃圾回收後 , 將區域 11 的內存中可用對象複製到區域 22
  • 複製時的可用對象在區域 22 緊密排列 , 不留空隙
  • 這樣區域 22 中可用內存區域是大塊完整的內存 , 不會產生內存碎片

當前使用區域 11 的內存區域內存不足時 , 會觸發上述操作 , 將當前區域 11 的存活對象 , 拷貝到區域 22 中 , 然後清理區域 11 內存 ;

分配回收內存時 , 只需要按照順序移動堆指針即可 , 不考慮碎片化等問題 , 簡單 , 高效 ;



2. 弊端 :


該垃圾回收算法缺陷也很明顯 , 就是會浪費一半內存空間 ;

有些對象的聲明週期等同於應用聲明週期 , 如 Android 中的 Application 等 , 該內存對象根本不釋放 , 持續往返復制這類長生存期的對象 , 會極大降低效率 ;

在這裏插入圖片描述





六、 標記-壓縮算法



1. 標記壓縮算法 : 與標記清除算法都需要先進行標記 ;


2. 標記壓縮算法流程 :

  • 首先標記可回收對象
  • 然後回收這些對象
  • 最後整理存活對象 , 將其拷貝到一塊連續內存中

該方法沒有複製算法浪費一半內存的問題 ;

該方法因爲多了一個壓縮過程 , 因此有額外的開銷 ;

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章