【Android 内存优化】内存抖动 ( 垃圾回收算法总结 | 分代收集算法补充 | 内存抖动排查 | 内存抖动操作 | 集合选择 )
文章目錄
- 一、 垃圾回收算法總結(jié)
- 二、 分代收集算法補(bǔ)充
- 三、 查看 Java 虛擬機(jī)
- 四、 獲取 Android 應(yīng)用可使用最大內(nèi)存
- 五、 內(nèi)存抖動標(biāo)志
- 六、 排查內(nèi)存抖動
- 七、 常見的造成內(nèi)存抖動操作
- 八、 從內(nèi)存優(yōu)化角度選擇集合
一、 垃圾回收算法總結(jié)
【Android 內(nèi)存優(yōu)化】垃圾回收算法 ( 內(nèi)存優(yōu)化總結(jié) | 常見的內(nèi)存泄漏場景 | GC 算法 | 標(biāo)記清除算法 | 復(fù)制算法 | 標(biāo)記壓縮算法 ) 介紹了 標(biāo)記清除算法 , 復(fù)制算法 , 標(biāo)記壓縮算法 , 三種垃圾回收算法 ;
【Android 內(nèi)存優(yōu)化】垃圾回收算法 ( 分代收集算法 | Serial 收集器 | ParNew 收集器 | Parallel Scavenge 收集器 | CMS 并發(fā)標(biāo)記清除收集器 ) 博客中介紹了分代收集算法 , 并對常用的垃圾收集器進(jìn)行了介紹 , 下面總結(jié)一下垃圾回收算法 , 與垃圾收集器 ;
1. 垃圾回收算法 :
① 標(biāo)記清除算法 : 標(biāo)記可回收的對象 , 之后將標(biāo)記的對象回收 ; 內(nèi)存碎片化 ;
② 復(fù)制算法 : 使用一半內(nèi)存 , 當(dāng)無法申請內(nèi)存時(shí) , 直接將有效對象拷貝到另一半內(nèi)存中 ; 浪費(fèi)內(nèi)存 , 效率低下 ;
③ 標(biāo)記壓縮算法 : 標(biāo)記回收內(nèi)存對象 , 整理內(nèi)存 ; 增加了開銷 ;
④ 分代收集算法 : 將內(nèi)存分為年輕代 , 老年代 , 持久代 , 三塊區(qū)域 ; 不同生命周期的內(nèi)存對象進(jìn)行不同的管理 ;
2. 垃圾收集器總結(jié) :
① Serial 收集器 : 年輕代 , 復(fù)制算法 , 單線程 GC , 暫停用戶線程 ;
② ParNew 收集器 : 年輕代 , 復(fù)制算法 , 多線程 GC , 暫停用戶線程 ;
③ Parallel Scavenge 收集器 : 年輕代 , 復(fù)制算法 , 多線程 GC , 暫停用戶線程 ( 關(guān)注吞吐量 ) ;
④ CMS ( Concurrent Mark Sweep ) 并發(fā)標(biāo)記清除收集器 ( 重點(diǎn) ) : 老年代 , 標(biāo)記-清除算法 , 多線程 GC , 與用戶線程并發(fā) ( 短時(shí)間暫停 ) ;
⑤ Parallel Old 收集器 : 老年代 , 標(biāo)記整理算法 , 多線程 GC , 暫停用戶線程 ;
⑥ Serial Old 收集器 : 老年代 , 標(biāo)記整理算法 , 單線程 GC , 暫停用戶線程 ;
二、 分代收集算法補(bǔ)充
【Android 內(nèi)存優(yōu)化】垃圾回收算法 ( 分代收集算法 | Serial 收集器 | ParNew 收集器 | Parallel Scavenge 收集器 | CMS 并發(fā)標(biāo)記清除收集器 ) 一、 分代收集算法 章節(jié)對分代收集算法做了下簡介 , 感覺沒有描述清楚 , 再補(bǔ)充下 :
1. 主流垃圾回收算法 : JVM , DVM 都采用了 分代收集算法 , 將內(nèi)存劃分成不同的內(nèi)存區(qū)域 , 不同的區(qū)域采用不同的垃圾收集算法 , 這是目前主流的 Java 虛擬機(jī)都在使用的垃圾回收算法 ;
2. 分代收集算法的核心思想是 :
- 不同的對象聲明周期不同 , 承擔(dān)的功能不同 ;
- 有些對象聲明周期比較長如 Android 中的 Application , Activity 等組件 ;
- 有的對象生命周期很短 , 如打印日志時(shí)創(chuàng)建打印內(nèi)容字符串 , 打印完畢后 , 該字符串對象馬上就沒用了 ;
- 這里要將不同的生命周期長度的對象 , 分別使用不同的垃圾回收機(jī)制進(jìn)行處理 , 這樣可以提高垃圾收集的效率 ;
3. 分治思想 : 垃圾對象收集時(shí) , 需要對整個(gè)內(nèi)存空間進(jìn)行掃描 , 這樣消耗很大 , 這里我們將內(nèi)存分區(qū) , 將生命周期短的對象放在一塊內(nèi)存中 , 生命周期長的對象放在另一塊內(nèi)存中 , 這樣針對不同的內(nèi)存塊 , 采取不同的垃圾回收策略 ; ( 分治思想 )
4. 內(nèi)存塊分塊 : 將 Java 內(nèi)存堆分為 年輕代 , 老年代 , 新創(chuàng)建的對象放在年輕代中 , 老對象轉(zhuǎn)移到老年代中 ;
5. 年輕代內(nèi)存分區(qū) : 年輕代內(nèi)存分為 Eden 和 Survivor 兩個(gè)區(qū)域 , Survivor 區(qū)域又分為 From Space 和 To Space ;
復(fù)制算法分區(qū) : 很明顯 Eden 和 Survivor 是復(fù)制算法中的兩個(gè)區(qū)域 , From Space 和 To Space 也是復(fù)制算法中的兩個(gè)區(qū)域 ;
6. 年輕代內(nèi)存策略 : 復(fù)制算法 ;
-
新對象存放 : 新創(chuàng)建的對象都放在年齡代內(nèi)存中的 Eden 區(qū)域中 ;
-
第一次復(fù)制算法 : 當(dāng) Eden 區(qū)域放滿時(shí) , 將存活的區(qū)域放到 Survivor 區(qū)域中的 From Space ( To Space ) 區(qū)域中 ;
-
第二次復(fù)制算法 : 當(dāng) From Space ( To Space ) 區(qū)域中存放后 , 會將年齡不足晉升的對象復(fù)制到另一側(cè)的 To Space ( From Space ) 區(qū)域中 ;
-
晉升機(jī)制 : 年輕代內(nèi)存中 , 一旦對象經(jīng)理的 GC 次數(shù)達(dá)到一定閾值 , 就會晉升到老年代內(nèi)存中 ;
7. 老年代內(nèi)存策略 : 標(biāo)記整理算法 ; Android 中使用的是 CMS 垃圾收集器 ;
三、 查看 Java 虛擬機(jī)
查看 Java 虛擬機(jī) : 在命令行中執(zhí)行 java -version , 即可查看當(dāng)前 java 虛擬機(jī)情況 ;
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>上述虛擬機(jī)是 HotSpot 虛擬機(jī)
四、 獲取 Android 應(yīng)用可使用最大內(nèi)存
OOM 就是應(yīng)用的內(nèi)存超過了堆的最大值 , 內(nèi)存分配的單位是進(jìn)程 , 每個(gè)進(jìn)程都會有一定的內(nèi)存限制 ,
1. 獲取當(dāng)前 Android 手機(jī)的最大使用用內(nèi)存 :
① 代碼獲取 : 調(diào)用 ActivityManager 對象的 getMemoryClass 方法獲得內(nèi)存對象 ;
② 執(zhí)行如下命令 :
adb shell getprop dalvik.vm.heapsize命令執(zhí)行結(jié)果 :
C:\Users\octop>adb shell getprop dalvik.vm.heapsize 512mC:\Users\octop>2. 獲取其它值 :
# 獲取 app 最大申請內(nèi)存, 超過就 OOM $ adb shell getprop dalvik.vm.heapsize 512m# 獲取初始內(nèi)存大小 $ adb shell getprop dalvik.vm.heapstartsize 8m# 正常情況下的內(nèi)存值 $ adb shell getprop dalvik.vm.heapgrowthlimit 192m3 . 指定極限大小 : 在 AndroidManifest.xml 中的 application 標(biāo)簽中指定 android:largeHeap 為 true , 為該進(jìn)程設(shè)置堆內(nèi)存極限大小 ;
五、 內(nèi)存抖動標(biāo)志
在 Android Profiler 中監(jiān)控 Memory 內(nèi)存 , 如果出現(xiàn)下圖樣式的內(nèi)存圖 , 說明出現(xiàn)了內(nèi)存抖動 ;
六、 排查內(nèi)存抖動
內(nèi)存抖動查找 , 直接跳轉(zhuǎn)到 Android Profiler 界面 , 點(diǎn)擊 Dump Java Heap 按鈕 , 保存一份內(nèi)存快照 , 找出消耗內(nèi)存最多的對象 , Allocations 個(gè)數(shù)最多的對象的類 , 該類對象大概率就是造成內(nèi)存抖動的原因 ;
七、 常見的造成內(nèi)存抖動操作
1. 日志打印 : 循環(huán)中使用 Log.i 函數(shù)打印日志 , 使用加號拼接字符串 , 尤其是每次拼接不同的字符串 , 每個(gè)字符串都需要創(chuàng)建釋放 , 這樣會造成內(nèi)存抖動 ;
2. 循環(huán)操作 : 在循環(huán)內(nèi)頻繁創(chuàng)建對象 , 與銷毀對象 ; 盡量將創(chuàng)建對象操作放在成員級別 , 或放在循環(huán)體外部 ;
八、 從內(nèi)存優(yōu)化角度選擇集合
HashMap 集合 : HashMap 有一個(gè)默認(rèn)大小 , 還有一個(gè)擴(kuò)容因子 ; 如默認(rèn)大小 100 , 擴(kuò)容因子 0.8 , 該集合只能存儲了 80 個(gè) , 之后如果還想向其中存儲數(shù)據(jù) , 就需要擴(kuò)容 , 擴(kuò)容時(shí) , 直接在默認(rèn)大小基礎(chǔ)上翻倍 ;
SparseArray 集合 : SparseArray 有默認(rèn)大小 , 沒有擴(kuò)容因子 , 每次擴(kuò)容 , 直接翻倍 ; SparseArray 的增刪查改都要進(jìn)行二分查找 ; SparseArray 的 Key 是 int 類型 , 其不必使用 Integer 包裝類型 ; 數(shù)據(jù)量很大時(shí) , 且需要鍵值對數(shù)據(jù)結(jié)構(gòu)時(shí) , 考慮使用 SparseArray 集合 ;
總結(jié)
以上是生活随笔為你收集整理的【Android 内存优化】内存抖动 ( 垃圾回收算法总结 | 分代收集算法补充 | 内存抖动排查 | 内存抖动操作 | 集合选择 )的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【Android 内存优化】垃圾回收算法
- 下一篇: 【Android 内存优化】图片文件压缩