【Android 內存優化】內存抖動 ( 垃圾回收算法總結 | 分代收集算法補充 | 內存抖動排查 | 內存抖動操作 | 集合選擇 )





一、 垃圾回收算法總結



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

【Android 內存優化】垃圾回收算法 ( 分代收集算法 | Serial 收集器 | ParNew 收集器 | Parallel Scavenge 收集器 | CMS 併發標記清除收集器 ) 博客中介紹了分代收集算法 , 並對常用的垃圾收集器進行了介紹 , 下面總結一下垃圾回收算法 , 與垃圾收集器 ;



1. 垃圾回收算法 :


① 標記清除算法 : 標記可回收的對象 , 之後將標記的對象回收 ; 內存碎片化 ;

② 複製算法 : 使用一半內存 , 當無法申請內存時 , 直接將有效對象拷貝到另一半內存中 ; 浪費內存 , 效率低下 ;

③ 標記壓縮算法 : 標記回收內存對象 , 整理內存 ; 增加了開銷 ;

④ 分代收集算法 : 將內存分爲年輕代 , 老年代 , 持久代 , 三塊區域 ; 不同生命週期的內存對象進行不同的管理 ;



2. 垃圾收集器總結 :


① Serial 收集器 : 年輕代 , 複製算法 , 單線程 GC , 暫停用戶線程 ;

② ParNew 收集器 : 年輕代 , 複製算法 , 多線程 GC , 暫停用戶線程 ;

③ Parallel Scavenge 收集器 : 年輕代 , 複製算法 , 多線程 GC , 暫停用戶線程 ( 關注吞吐量 ) ;

④ CMS ( Concurrent Mark Sweep ) 併發標記清除收集器 ( 重點 ) : 老年代 , 標記-清除算法 , 多線程 GC , 與用戶線程併發 ( 短時間暫停 ) ;

⑤ Parallel Old 收集器 : 老年代 , 標記整理算法 , 多線程 GC , 暫停用戶線程 ;

⑥ Serial Old 收集器 : 老年代 , 標記整理算法 , 單線程 GC , 暫停用戶線程 ;





二、 分代收集算法補充



【Android 內存優化】垃圾回收算法 ( 分代收集算法 | Serial 收集器 | ParNew 收集器 | Parallel Scavenge 收集器 | CMS 併發標記清除收集器 ) 一、 分代收集算法 章節對分代收集算法做了下簡介 , 感覺沒有描述清楚 , 再補充下 :


1. 主流垃圾回收算法 : JVM , DVM 都採用了 分代收集算法 , 將內存劃分成不同的內存區域 , 不同的區域採用不同的垃圾收集算法 , 這是目前主流的 Java 虛擬機都在使用的垃圾回收算法 ;


2. 分代收集算法的核心思想是 :

  • 不同的對象聲明週期不同 , 承擔的功能不同 ;
  • 有些對象聲明週期比較長如 Android 中的 Application , Activity 等組件 ;
  • 有的對象生命週期很短 , 如打印日誌時創建打印內容字符串 , 打印完畢後 , 該字符串對象馬上就沒用了 ;
  • 這裏要將不同的生命週期長度的對象 , 分別使用不同的垃圾回收機制進行處理 , 這樣可以提高垃圾收集的效率 ;

3. 分治思想 : 垃圾對象收集時 , 需要對整個內存空間進行掃描 , 這樣消耗很大 , 這裏我們將內存分區 , 將生命週期短的對象放在一塊內存中 , 生命週期長的對象放在另一塊內存中 , 這樣針對不同的內存塊 , 採取不同的垃圾回收策略 ; ( 分治思想 )


4. 內存塊分塊 : 將 Java 內存堆分爲 年輕代 , 老年代 , 新創建的對象放在年輕代中 , 老對象轉移到老年代中 ;


5. 年輕代內存分區 : 年輕代內存分爲 Eden 和 Survivor 兩個區域 , Survivor 區域又分爲 From Space 和 To Space ;

複製算法分區 : 很明顯 Eden 和 Survivor 是複製算法中的兩個區域 , From Space 和 To Space 也是複製算法中的兩個區域 ;


6. 年輕代內存策略 : 複製算法 ;

  • 新對象存放 : 新創建的對象都放在年齡代內存中的 Eden 區域中 ;

  • 第一次複製算法 : 當 Eden 區域放滿時 , 將存活的區域放到 Survivor 區域中的 From Space ( To Space ) 區域中 ;

  • 第二次複製算法 : 當 From Space ( To Space ) 區域中存放後 , 會將年齡不足晉升的對象複製到另一側的 To Space ( From Space ) 區域中 ;

  • 晉升機制 : 年輕代內存中 , 一旦對象經理的 GC 次數達到一定閾值 , 就會晉升到老年代內存中 ;


7. 老年代內存策略 : 標記整理算法 ; Android 中使用的是 CMS 垃圾收集器 ;





三、 查看 Java 虛擬機



查看 Java 虛擬機 : 在命令行中執行 java -version , 即可查看當前 java 虛擬機情況 ;

C:\Users\octop>java -version
java version "1.8.0_221"
Java(TM) SE Runtime Environment (build 1.8.0_221-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.221-b11, mixed mode)

C:\Users\octop>

在這裏插入圖片描述

上述虛擬機是 HotSpot 虛擬機





四、 獲取 Android 應用可使用最大內存



OOM 就是應用的內存超過了堆的最大值 , 內存分配的單位是進程 , 每個進程都會有一定的內存限制 ,


1. 獲取當前 Android 手機的最大使用用內存 :


① 代碼獲取 : 調用 ActivityManager 對象的 getMemoryClass 方法獲得內存對象 ;

② 執行如下命令 :

adb shell getprop dalvik.vm.heapsize

命令執行結果 :

C:\Users\octop>adb shell getprop dalvik.vm.heapsize
512m

C:\Users\octop>

在這裏插入圖片描述


2. 獲取其它值 :

# 獲取 app 最大申請內存, 超過就 OOM 
$ adb shell getprop dalvik.vm.heapsize
512m

# 獲取初始內存大小
$ adb shell getprop dalvik.vm.heapstartsize
8m

# 正常情況下的內存值
$ adb shell getprop dalvik.vm.heapgrowthlimit
192m

3 . 指定極限大小 :AndroidManifest.xml 中的 application 標籤中指定 android:largeHeap 爲 true , 爲該進程設置堆內存極限大小 ;





五、 內存抖動標誌



在 Android Profiler 中監控 Memory 內存 , 如果出現下圖樣式的內存圖 , 說明出現了內存抖動 ;

在這裏插入圖片描述





六、 排查內存抖動



內存抖動查找 , 直接跳轉到 Android Profiler 界面 , 點擊 Dump Java Heap 按鈕 , 保存一份內存快照 , 找出消耗內存最多的對象 , Allocations 個數最多的對象的類 , 該類對象大概率就是造成內存抖動的原因 ;

在這裏插入圖片描述





七、 常見的造成內存抖動操作



1. 日誌打印 : 循環中使用 Log.i 函數打印日誌 , 使用加號拼接字符串 , 尤其是每次拼接不同的字符串 , 每個字符串都需要創建釋放 , 這樣會造成內存抖動 ;


2. 循環操作 : 在循環內頻繁創建對象 , 與銷燬對象 ; 儘量將創建對象操作放在成員級別 , 或放在循環體外部 ;





八、 從內存優化角度選擇集合



HashMap 集合 : HashMap 有一個默認大小 , 還有一個擴容因子 ; 如默認大小 100 , 擴容因子 0.8 , 該集合只能存儲了 80 個 , 之後如果還想向其中存儲數據 , 就需要擴容 , 擴容時 , 直接在默認大小基礎上翻倍 ;


SparseArray 集合 : SparseArray 有默認大小 , 沒有擴容因子 , 每次擴容 , 直接翻倍 ; SparseArray 的增刪查改都要進行二分查找 ; SparseArray 的 Key 是 int 類型 , 其不必使用 Integer 包裝類型 ; 數據量很大時 , 且需要鍵值對數據結構時 , 考慮使用 SparseArray 集合 ;

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