當我們剛開始接觸Android時筹麸,可能關注的比較多的是如何實現(xiàn)某個功能枫夺,但學到一定程度的時候将宪,我們會發(fā)現(xiàn)無論一個應用多么炫酷,如果運行特別慢橡庞,或者說很耗內存较坛,這將會帶來很差的用戶體驗,所以說扒最,性能優(yōu)化變得尤為重要丑勤。
一. 垃圾回收機制
自動管理內存和回收機制,垃圾回收器負責回收程序中已經不使用吧趣,但是仍然被各種對象占用的內存确封,將程序員從繁重、危險的內存管理工中解放出來再菊。
缺點:可能會占用大量資源爪喘。
Android有垃圾回收機制,無需手動管理內存纠拔,Android系統(tǒng)會自動跟蹤所有對象秉剑,并釋放那些不再使用的對象。
二. Android中的垃圾回收機制
新生代
- 大多數(shù)新建的對象都位于Eden區(qū)稠诲。
- 當Eden區(qū)域被對象填滿時侦鹏,就會執(zhí)行Minor GC诡曙,并把所有存活下來的對象轉移到其中一個survivor區(qū)。
- Survivor Space:S0略水、S1有兩個价卤,存放每次垃圾回收后存活的對象。
- Minor GC同樣會檢查survivor區(qū)存活下來的對象渊涝,并把它們轉移到另一個survivor區(qū)慎璧,這樣在一段時間內總是有一個空的survivor區(qū)。
老年代
- 存放長期存活的對象和經過多次Minor GC后跨释,依然存活下來的對象胸私。
- 滿了進行Major GC。
永久代
- 存放方法區(qū)鳖谈,方法區(qū)中有要加載的類信息岁疼、靜態(tài)變量、final類型的常量缆娃、屬性和方法信息捷绒。
三. 內存泄露
- 應用程序分配了大量不能被回收的對象。
- 系統(tǒng)可分配內存越來越少贯要。
- 新對象的創(chuàng)建需要內存不夠暖侨。
- GC之后再分配。
- 60fps郭毕。
四. 內存抖動
因為在短時間內大量的對象被創(chuàng)建又馬上被釋放,瞬間產生大量的對象會嚴重占用新生代的內存區(qū)域函荣,當達到閾值显押,剩余空間不夠的時候,會觸發(fā)GC從而導致剛產生的對象又很快被回收傻挂,即使每次分配的對象占用了很少的內存乘碑,但是他們疊加在一起會增加Heap的壓力,從而觸發(fā)更多其他類型的GC金拒,這個操作又可能會影響到幀率兽肤,并使得用戶感知到性能問題。
五. 工具
Memory Monitor
藍色部分表示使用內存绪抛,灰色部分表示空閑內存资铡,峰值表示發(fā)生了一次垃圾回收。
特點:
- 方便顯示內存使用和GC情況幢码。
- 快速定位卡頓是否和GC有關笤休。
- 快速定位Crash是否和內存占用過高有關。
- 快速定位潛在的內存泄露問題症副。
- 簡單易用店雅。
- 不能準確定位問題政基。
Allocation Tracker
跟蹤對象內存分配的工具∧掷玻可以追蹤應用程序在運行時所有已分配的內存沮明,所有已創(chuàng)建的對象,對象的數(shù)量和他們所占用的內存大小以及這些對象是在哪些方法中創(chuàng)建的窍奋,用于檢測內存抖動現(xiàn)象荐健。
特點:
- 定位代碼中分配的對象的類型、大小费变、時間摧扇、線程、堆棧等信息挚歧。
- 定位內存抖動問題扛稽。
- 配合Heap Viewer一起定位內存抖動問題。
- 使用復雜滑负。
Heap Viewer
實時展示應用程序運行時所有已分配的對象的數(shù)量在张、大小以及類型信息。用于檢測內存泄露矮慕。
特點:
- 內存快照信息帮匾。
- 每次GC之后收集一次信息。
- 查找內存泄露利器痴鳄。
- 使用復雜瘟斜。
六. 實例
這里有一個存在內存泄露的例子,下載地址:https://github.com/lzyzsd/MemoryBugs
主要使用MemoryMonitor痪寻, AllocationTracker螺句,HeapDump以及LeakCanary等工具來查找潛在的內存問題,并嘗試解決橡类。
解決過程記錄如下:
運行該程序蛇尚,可以看到主界面如下圖所示:
有一個TextView,一個半圓顾画,兩個按鈕取劫。
這里先點擊第一個按鈕StartActivityB,這時會彈出一個Toast:請注意查看通知欄LeakMemory研侣,點開通知欄的通知谱邪,看到有提示MainActivity has leaked,意思就是MainActivity出現(xiàn)內存泄露庶诡,如下圖:
通過分析虾标,是由于static類型的sTextView引用了mContext導致了MainActivity發(fā)生了內存泄漏,看到這里很多人估計會一臉懵逼,難道手機會自帶檢測內存泄露的工具嗎璧函?其實不是傀蚌,看程序源代碼,不難發(fā)現(xiàn)在build.gradle中引入了一個叫LeakCanary的工具蘸吓,具體代碼如下:
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3.1' // or 1.4-beta1
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3.1' // or 1.4-beta1
testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3.1' // or 1.4-beta1
并且在MyApplication中LeakCanary.install(this);
由于static類型的變量是不會被垃圾回收的善炫,所以導致了MainActivity的內存泄露,解決方案就是去掉static库继,修改代碼:
// private static TextView sTextView;
private TextView mTextView;
接著看一下半圓的繪制是否存在問題箩艺,先看代碼:
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
RectF rect = new RectF(0, 0, 100, 100);
Paint paint = new Paint();
paint.setColor(Color.RED);
paint.setStrokeWidth(4);
canvas.drawArc(rect, 0, 180, true, paint);
}
果然有問題,由于onDraw()方法調用比較頻繁宪萄,所以一般盡量避免在onDraw()方法中創(chuàng)建對象艺谆,這里恰恰就在onDraw()方法中創(chuàng)建對象,所以這里的修改方案是把創(chuàng)建對象放到定義成員變量的位置拜英。代碼如下:
private RectF mRectF = new RectF(0, 0, 100, 100);
private Paint mPaint = new Paint();
這時的onDraw()方法如下:
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPaint.setColor(Color.RED);
mPaint.setStrokeWidth(4);
canvas.drawArc(mRect, 0, 180, true, mPaint);
}
OK静汤,再次運行程序,點擊按鈕StartActivityB居凶,沒有出現(xiàn)LeakCanary的提示虫给。
在Android Studio中打開Android Monitor -> Memory,不斷點擊按鈕StartAllocation侠碧,不斷的發(fā)生內存回收和分配抹估,會出現(xiàn)以下狀況,這就是我們上邊所說的內存抖動弄兜。
配合Allocation Tracking药蜻,在內存抖動開始時點擊Start Allocation Tracking按鈕,在抖動結束后再點擊一下替饿。會得到如下圖所示的.alloc文件:
選擇Group by Allocator语泽,然后點擊最外圈的綠色,然后雙擊右面的Activity盛垦,把Activity展開后會發(fā)現(xiàn)進行很多Rect和StringBuilder對象的創(chuàng)建湿弦。
問題就在這里瓤漏,看代碼:
private void startAllocationLargeNumbersOfObjects() {
Toast.makeText(this, "請注意查看MemoryMonitor 以及AllocationTracker", Toast.LENGTH_SHORT).show();
for (int i = 0; i < 10000; i++) {
Rect rect = new Rect(0, 0, 100, 100);
System.out.println("-------: " + rect.width());
}
}
可以看到在for循環(huán)中一直創(chuàng)建對象及字符串的拼接腾夯。
修改方案是把Rect對象的創(chuàng)建放到成員變量中,在onCreate中進行初始化蔬充,為了避免在logcat輸出時產生大量的String對象蝶俱,修改方案是在onCreate中把String對象創(chuàng)建好,這樣就不會重復創(chuàng)建了饥漫,還要把里面的字符串提取出來榨呆,放到strings.xml中,有的要設置為static final類型的字符串資源庸队,修改代碼如下:
成員變量:
public static final String LINE_TAG = "-------: ";
private Rect mRect;
private String mLogString;
onCreate():
mRect = new Rect(0, 0, 100, 100);
mLogString = LINE_TAG + mRect.width();
startAllocationLargeNumbersOfObjects()
private void startAllocationLargeNumbersOfObjects() {
Toast.makeText(this, R.string.memory_monitor, Toast.LENGTH_SHORT).show();
for (int i = 0; i < 10000; i++) {
System.out.println(mLogString);
}
}
strings.xml:
<resources>
<string name="app_name">MemoryBugs</string>
<string name="memory_monitor">請注意查看MemoryMonitor 以及AllocationTracker</string>
<string name="leakmemory">請注意查看通知欄LeakMemory</string>
<string name="hello_world">Hello World!</string>
</resources>
以上解決了三個問題积蜻,那么怎么檢測是否還存在內存泄露呢闯割?還有一個工具叫Heap Viewer,這個工具可以實時展示應用程序運行時所有已分配的對象的數(shù)量竿拆、大小以及類型信息宙拉,可以檢測內存泄露。
在手機屏幕上點擊StartActivityB丙笋,在Android Studio中點擊Dump Java Heap谢澈,選擇Package Tree View,找到我們的程序御板,可以看到MainActivity還沒有被垃圾回收锥忿。
手動進行一下垃圾回收,再次點擊Dump Java Heap怠肋,可以看到如下效果:
這時看到MainActivity已經被垃圾回收了敬鬓,不存在內存泄漏問題了。