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