android中内存泄露,Android中的内存泄露
編輯推薦:
本文來自于csdn,本文主要從java的內存模型講起,最終舉出幾個內存泄露的例子和解決方案。
java運行時內存模型
具體信息:http://gityuan.com/2016/01/09/java-memory/
回收算法
JVM回收算法主要有兩種
1.引用計數法:每個對象有一個引用計數器,當對象被引用一次時計數器加一,引用失效計數器減一。當計數器為0時表示對象可以被回收。(由于無法解決相互引用問題而被廢棄)
2.可達性算法:從GC ROOT節點開始遍歷,可以連通的對象都是活對象。無法到達的對象可以被回收。
可以作為GC ROOT節點的對象
虛擬機棧的棧幀的局部變量表引用的對象
本地方法棧JNI引用的對象
方法區的靜態變量和常量所引用的對象
例子
public
class GCDemo {
public static void main(String[] args) {
GcObject obj1 = new GcObject();
GcObject obj2 = new GcObject();
obj1.instance = obj2;
obj2.instance = obj1;
obj1 = null;
obj2 = null;
}
}
class GcObject {
public Object instance = null;
}
引用計數法內存圖
1.step1:GcObject實例1的引用計數+1,目前為1
2.step2:GcObject實例2的引用計數+1,目前為1
3.step3:GcObject實例2的引用計數+1,目前為2
4.step4:GcObject實例1的引用計數+1,目前為2
5.obj1 = null:GcObject實例1引用計數-1,目前為1
6.obj2 = null:GcObject實例2引用計數-1,目前為1
到此為止,GcObject實例1和2都無法使用,但是引用計數不為0,發生內存泄露。
可達性算法內存圖
由可以被作為GC ROOT對象來看,虛擬機棧中obj1和obj2是兩個GC ROOT起點,由于最終都將obj1與obj2設置為了null。因此GcObject1和2都無法可達,因此可以被回收。
在Java中,內存泄漏就是存在一些被分配的對象,這些對象有下面兩個特點,首先,這些對象是可達的,即在有向圖中,存在通路可以與其相連;其次,這些對象是無用的,即程序以后不會再使用這些對象。如果對象滿足這兩個條件,這些對象就可以判定為Java中的內存泄漏,這些對象不會被GC所回收,然而它卻占用內存。
Android中常見的內存泄露
Android中最常見的內存泄露是關于Activity的內存泄漏。其核心問題在于在Activity生命周期之外仍有其引用。從內存模型角度來講,它在GC
ROOTING時可達,但是它的onDestroy已經被執行。既它從我們期待的行為上來說應該被標識為可以被回收。但它仍然可達。以下就列出最常見的幾種情況,請記住核心矛盾:Activity生命周期之外仍有其引用
Handler導致的內存泄漏
public
class SampleActivity extends Activity {
private final Handler mLeakyHandler = new
Handler() {
@Override
public void handleMessage(Message msg) {
// ...
}
}
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
// 延時10分鐘發送一個消息
mLeakyHandler.postDelayed(new Runnable() {
@Override
public void run() { }
}, 60 * 10 * 1000);
// 返回前一個Activity
finish();
}
}
思考上面代碼,它會發生嚴重的內存泄露。首先,mLeakyHandler被聲明為了一個匿名內部類(自己思考,上述代碼有幾個匿名內部類),它隱式的持有了外部類Activity的強引用。之后,handler發出了一個10分鐘的延時消息,接著Activity殺掉了自己。問題的關鍵在于handler發出的消息Message會在消息隊列里存i在10分鐘。它持有發出消息的handler的引用。而handler又持有Activity的強引用。這就導致Activity在其生命周期之外仍有強引用,發生了嚴重的內存泄露。
注意,問題的核心在于內部類以及匿名內部類都會隱式的持有外部類的強引用。這就導致一個類的生命不在由一個因素控制,變為了多個因素。在我們認為該結束的時候而沒有產生正確的行為。解決的方案很簡單,也很通用:
用靜態內部類。
在靜態內部類根據需求使用弱引用修飾需要引用的外部類資源。
public
class SampleActivity extends Activity {
/**
* 匿名類的靜態實例不會隱式持有他們外部類的引用
*/
private static final Runnable sRunnable = new
Runnable() {
@Override
public void run() {
}
};
private final MyHandler mHandler = new MyHandler(this);
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
// 延時10分鐘發送一個消息.
mHandler.postDelayed(sRunnable, 60 * 10 * 1000);
// 返回前一個Activity
finish();
}
/**
* 靜態內部類的實例不會隱式持有他們外部類的引用。
*/
private static class MyHandler extends Handler
{
private final WeakReference
mActivity;
public MyHandler(SampleActivity activity)
{
mActivity = new WeakReference(activity);
}
@Override
public void handleMessage(Message msg) {
SampleActivity activity = mActivity.get();
if (activity != null) {
// ...
}
}
}
}
static變量導致的內存泄漏
static變量在進程啟動的時候被分配內存空間。在進程被殺死的時候釋放內存空間。因此,它的生命周期是貫穿整個應用的生命周期的。也就是說,當你的Activity被靜態變量染指到。那么又會導致Activity在其生命周期之外仍有強引用,發生內存泄露。
常見的場景如下:
static
Context
static View
兩者的本質是一樣的,因為View內部持有Context的引用。第一種最常見的就是單例模式的實現,將具體的Activity的Context傳遞進去構造單例對象,導致泄露。第二種是當View需要加載的資源比較大時,想一勞永逸。而產生內存泄露。
解決方法依然通用且簡單,在Activity聲明周期外使用Context盡量去使用Application的Context
Activiity內部聲明的靜態資源要及時釋放, 在相應的聲明周期方法中做好收尾工作
注冊與解注冊
在andorid開發中,我們經常會在Activity的onCreate中注冊廣播接受器、EventBus等,如果忘記成對的使用反注冊,可能會引起內存泄漏。開發過程中應該養成良好的相關,在onCreate或onResume中注冊,要記得相應的在onDestroy或onPause中反注冊。
創建與關閉
在android中,資源性對象比如Cursor、File、Bitmap、視頻等,系統都用了一些緩沖技術,在使用這些資源的時候,如果我們確保自己不再使用這些資源了,要及時關閉,否則可能引起內存泄漏。因為有些操作不僅僅只是涉及到Dalvik虛擬機,還涉及到底層C/C++等的內存管理,不能完全寄希望虛擬機幫我們完成內存管理。
在這些資源不使用的時候,記得調用相應的類似close()、destroy()、recycler()、release()等函數,這些函數往往會通過jni調用底層C/C++的相應函數,完成相關的內存釋放。
Cursor對象及時關閉
IO對象及時關閉
集合造成的內存泄漏
Vector
v = new Vector(10);
for (int i = 1; i < 100; i++) {
Object o = new Object();
v.add(o);
o = null;
}
畫個簡單的內存圖就可以看出在棧中分配的v指向了在堆中分配的十個空間,雖然每次置為null,但是將V作為GC
ROOT的起點仍然可達每一個在堆中的Object對象,而我們本意是將每一個Object對象置為了null。
解決方案很簡單:v = null 。本質上是斷掉GC ROOT的起點。
總結
以上是生活随笔為你收集整理的android中内存泄露,Android中的内存泄露的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 一套代码编译出ios和android,H
- 下一篇: android sina oauth2.