剛?cè)腴T的童鞋肯能都會有一個(gè)疑問,Java不是有虛擬機(jī)了么,內(nèi)存會自動(dòng)化管理,我們就不必要手動(dòng)的釋放資源了,反正系統(tǒng)會給我們完成。其實(shí)Java中沒有指針的概念,但是指針的使用方式依然存在,一味的依賴系統(tǒng)的gc,很容易就造成了內(nèi)存的浪費(fèi)。
Java的內(nèi)存管理機(jī)制會自動(dòng)回收無用對象所占用的內(nèi)存,減輕手工管理內(nèi)存的負(fù)擔(dān)
1、C/C++: 從申請、使用、釋放都需要手工管理
2、Java:無用的對象的內(nèi)存會被自動(dòng)回收
什么樣的對象是無用的對象
1、Java通過引用來操作一個(gè)具體的對象,引用類似于C 中的指針。一個(gè)對象可以持有其他對象的引用。
2、從一組根對象(GC Roots)開始,按對象之前的引用關(guān)系遍歷所有對象,在遍歷過程中標(biāo)記所有的可達(dá)對象。如果一個(gè)對象由根對象出發(fā)不可達(dá),則將它作為垃圾收集。
GCRoot 都有哪些?
1、 Class:由系統(tǒng)的類加載器加載的類對象
2、 Static Fields
3、 Thread:活著的線程
4、 Stack Local: java方法的局部變量或參數(shù)
5、 JNI Local: JNI方法中的局部引用
6、 JNI Global: 全局的JNI引用
7、 Monitor used: 用于同步的監(jiān)控對象
8、Help by VM: 用于JVM特殊目的由GC保留的對象
Java程序中的內(nèi)存泄漏
對象的內(nèi)存在分配之后無法通過程序的執(zhí)行邏輯釋放對該對象的引用,不能被回收該對象所占內(nèi)存
內(nèi)存泄漏的危害
1、 引起OutOfMemoryError
2、 內(nèi)存占用高時(shí)JVM虛擬機(jī)會頻繁觸發(fā)GC, 影響程序響應(yīng)速度
3、內(nèi)存占用大的程序容易被各種清理優(yōu)化程序中止,用戶也更傾向于卸載這些程序
Android應(yīng)用的開發(fā)語言為Java,每個(gè)應(yīng)用最大可使用的堆內(nèi)存受到Android系統(tǒng)的限制
Android每一個(gè)應(yīng)用的堆內(nèi)存大小有限
1、 通常的情況為16M-48M
2、 通過ActivityManager的getMemoryClass()來查詢可用堆內(nèi)存限制
3、3.0(HoneyComb)以上的版本可以通過largeHeap=“true”來申請更多的堆內(nèi)存
Nexus S(4.2.1):normal 192, largeHeap 512
4、如果試圖申請的內(nèi)存大于當(dāng)前余下的堆內(nèi)存就會引發(fā)OutOfMemoryError()
5、應(yīng)用程序由于各方面的限制,需要注意減少內(nèi)存占用,避免出現(xiàn)內(nèi)存泄漏。
用MAT工具來檢測內(nèi)存泄漏
在試圖窗口中新建一個(gè)Memory Analysis會出現(xiàn)一個(gè)
沒有的可以去http://www.eclipse.org/mat/downloads.php安裝一下MAT
在Android 的調(diào)試環(huán)境DDMS下,找到Heap dump
Dump下當(dāng)前內(nèi)存中的鏡像文件,*****.hprof
能清楚的看到每一個(gè)部分暫用的內(nèi)存大小。
也可以切換試圖,group查看不同包不同類的占用細(xì)節(jié)。
Heap dump
包含了觸發(fā)Heap dump生成的時(shí)刻Java進(jìn)程的內(nèi)存快照,主要內(nèi)容為各個(gè)Java類和對象在堆內(nèi)存中的分配情況
Memory Analyzer Tool (MAT)
常見內(nèi)存泄露原因
Context對象泄漏
1、如果一個(gè)類持有Context對象的強(qiáng)引用,就需要檢查其生存周期是否比Context對象更長。否則就可能發(fā)生Context泄漏。
2、View持有其創(chuàng)建所在Context對象的引用,如果將View對象傳遞給其它生存周期比View所在Context更長的強(qiáng)引用,就可能會引起內(nèi)存泄漏。
例如View#setTag(int, Object)的內(nèi)存泄漏https://code.google.com/p/android/issues/detail?id=18273
3、把Context對象賦給static變量。
避免Context對象泄漏Checklist
1、檢查所有持有對Context對象強(qiáng)引用的對象的生命周期是否超出其所持有的Context對象的生命周期。
2、檢查有沒有把View傳出到View所在Context之外的地方,如果有的話就需要檢查生命周期。
3、工具類中最好不要有Context成員變量,盡量在調(diào)用函數(shù)時(shí)直接通過調(diào)用參數(shù)傳入。如果必須有Context成員變量時(shí),可以考慮使用WeakReference來引用Context對象。
4、View持有其創(chuàng)建所在Context對象的引用,如果將View對象傳遞給其它生存周期比View所在Context更長的強(qiáng)引用,就可能會引起內(nèi)存泄漏。
5、 檢查把Context或者View對象賦給static變量的地方,看是否有Context泄漏。
6、檢查所有把View放入容器類的地方(特別是static容器類),看是否有內(nèi)存泄漏。7、使用WeakHashMap也需要注意有沒有value-key的引用。
7、盡量使用ApplicationContext。
Handler對象泄漏
1、發(fā)送到Handler的Message實(shí)際上是加入到了主線程的消息隊(duì)列等待處理,每一個(gè)Message持有其目標(biāo)Handler的強(qiáng)引用。
如我們通常使用的匿名內(nèi)部類Handler
上面是一段簡單的Handler的使用。當(dāng)使用內(nèi)部類(包括匿名類)來創(chuàng)建Handler的時(shí)候,Handler對象會隱式地持有一個(gè)外部類對象(通常是一個(gè)Activity)的引用,因?yàn)閂iew會依附著一個(gè)Activity。而Handler通常會伴隨著一個(gè)耗時(shí)的后臺線程(例如從網(wǎng)絡(luò)拉取圖片)一起出現(xiàn),這個(gè)后臺線程在任務(wù)執(zhí)行完畢(例如圖片下載完畢)之后,通過消息機(jī)制通知Handler,然后Handler把圖片更新到界面。然而,如果用戶在網(wǎng)絡(luò)請求過程中關(guān)閉了Activity,正常情況下,Activity不再被使用,它就有可能在GC檢查時(shí)被回收掉,但由于這時(shí)線程尚未執(zhí)行完,而該線程持有Handler的引用(不然它怎么發(fā)消息給Handler?),這個(gè)Handler又持有Activity的引用,就導(dǎo)致該Activity無法被回收(即內(nèi)存泄露),直到網(wǎng)絡(luò)請求結(jié)束(例如圖片下載完畢)。另外,如果你執(zhí)行了Handler的postDelayed()方法,該方法會將你的Handler裝入一個(gè)Message,并把這條Message推到MessageQueue中,那么在你設(shè)定的delay到達(dá)之前,會有一條MessageQueue -> Message -> Handler -> Activity的鏈,導(dǎo)致你的Activity被持有引用而無法被回收。
當(dāng)然,應(yīng)為是Handler對外部持有引用的原因,我們就可以將Activity設(shè)置為一個(gè)弱引用,在不必要的時(shí)候,不再執(zhí)行內(nèi)部方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | <span style= "font-size:18px;" > /** * @author zhoushengtao * @since 2013-12-16 下午3:25:36 */ import android.app.Activity; importandroid.content.Context; importandroid.os.Handler; importandroid.os.Message; importjava.lang.ref.WeakReference; publicclass WeakRefHandler extends Handler { WeakReference<context> mWeakContext; public WeakRefHandler(Context context) { mWeakContext = newWeakReference<context>(context); } @Override public void handleMessage(Message msg) { if ((mWeakContext.get() instanceofActivity )&& ((Activity)mWeakContext.get()).isFinishing()) return ; if (mWeakContext== null ){ return ; } super .handleMessage(msg); } }</context></context></span> |
2、Non-staticinner class 和anonymous class持有其outer class的引用。
Drawable.Callback引起的內(nèi)存泄漏
Drawable對象持有Drawable.callback的引用。當(dāng)把一個(gè)Drawable對象設(shè)置到一個(gè)View時(shí),Drawable對象會持有該View的引用作為Drawable.Callback
避免Drawable.Callback引起內(nèi)存泄漏
盡量不要在static成員中保存Drawable對象
對于需要保存的Drawable對象, 在需要時(shí)調(diào)用Drawable#setCallback(null).
其他內(nèi)存泄漏<喎?"http://www.2cto.com/kf/ware/vc/" target="_blank" class="keylink">vc3Ryb25nPjwvcD4KPHAgYWxpZ249"left"> 1、Android DigitalClock引起的內(nèi)存泄漏http://code.google.com/p/android/issues/detail?id=17015
2、使用Map容器類時(shí),作為Key 的類沒有正確的實(shí)現(xiàn)hashCode和equal函數(shù)
其他內(nèi)存泄漏
JNI程序中的內(nèi)存泄漏
1、 Malloc/free。
2、 JNI Global reference
Thread-Local Variable
1、 相當(dāng)于Thread對象的成員變量, 可以存儲線程相關(guān)的狀態(tài)
2、 如果thread是alive狀態(tài),那么Thread-Local中的對象就無法被GC。
進(jìn)程內(nèi)存占用監(jiān)測工具
Dumpsys
$ dumpsys meminfo [pid]
Procrank + Shell腳本
#procrank
1、 VSS - Virtual Set Size 虛擬耗用內(nèi)存(包含共享庫占用的內(nèi)存)
2、 RSS - Resident Set Size 實(shí)際使用物理內(nèi)存(包含共享庫占用的內(nèi)存)
3、 PSS - Proportional Set Size 實(shí)際使用的物理內(nèi)存(比例分配共享庫占用的內(nèi)存)
4、 USS - Unique Set Size 進(jìn)程獨(dú)自占用的物理內(nèi)存(不包含共享庫占用的內(nèi)存)
Shell腳本
#!/bin/bash
while true; do
adbshell procrank " grep "com.qihoo360.mobilesafe"
sleep1
done
當(dāng)然,部分機(jī)型的sh都是經(jīng)過第三方手機(jī)商精簡過的,很多命令都用不了。Procrank,就是一個(gè)經(jīng)常被精簡掉的命令。
鑒于此:
自己寫了一個(gè)小工具,檢測內(nèi)存的實(shí)時(shí)變化,
Github地址:https://github.com/stchou/JasonTest
小結(jié)
1. 保存對象前要三思
I. 對象本身有無隱含的引用
II. 保存后何時(shí)能夠回收
2. 要了解常見的隱含引用
I. anonymous class outer class
II. View to context
3. 要通過各種工具檢查內(nèi)存占用是否有異常
4. 創(chuàng)建大對象時(shí),要檢查它的生命周期
/**
* @author zhoushengtao(周圣韜)
* @since 2014年5月21日 下午6:18:29