天天看點

android final記憶體洩漏,Android記憶體洩漏

記憶體洩漏,簡單而言就是該被釋放的對象沒有被釋放掉,一直被某個或某些執行個體引用,導緻無法被GC回收。

Java的記憶體配置設定政策

Java程式運作時,記憶體配置設定政策有三種:靜态配置設定、棧配置設定、堆配置設定。所對應的記憶體空間即為:靜态存儲區(方法區)、棧區、堆區。

靜态儲存區:編譯時就配置設定好,在程式整個運作期間都存在。它主要存放靜态資料和常量;

棧區:當方法執行時,會在棧區記憶體中建立方法體内部的局部變量,方法結束後自動釋放記憶體;

堆區:通常存放 new 出來的對象。由 Java 垃圾回收器回收。

局部變量的基本資料和引用存儲在棧中,引用的實體存儲在堆中。

因為他們屬于方法中的變量,生命周期随方法而結束。

成員變量全部存儲在堆中(包括基本資料、引用和引用的實體對象)。

因為他們屬于類,類對象被new的時候,會存儲在堆中。

Java中如何管理記憶體

記憶體配置設定:程式控制,new 對象申請記憶體空間(基本類型除外),所有對象都在堆中配置設定空間。

垃圾回收:GC完成記憶體的釋放。

android final記憶體洩漏,Android記憶體洩漏

Java記憶體管理

Java使用有向圖的方式進行記憶體管理,可以消除引用循環的問題。如果某個對象與這個根頂點不可達,那麼可以認為這個(這些)對象不再被引用,可以被 GC 回收。

還有一個常見的記憶體管理技術就是使用引用計數器。雖然執行效率高,但是精度低(很難處理循環引用問題。)

Java中的記憶體洩漏

這些對象是可達的。

這些對象是無用的。

即因為可達而不會被GC回收,但是又無用占據了記憶體,是以造成了記憶體洩漏。

android final記憶體洩漏,Android記憶體洩漏

Java中的記憶體洩漏

Android中常見的記憶體洩漏

集合中對象沒清理造成的記憶體洩漏

我們通常把一些對象的引用加入到了集合容器(比如ArrayList)中,當我們不需要該對象時,并沒有把它的引用從集合中清理掉,這樣這個集合就會越來越大。如果這個集合是static的話,那情況就更嚴重了。

是以要在退出程式之前,将集合裡的東西clear,然後置為null,再退出程式。

解決方案:

在Activity退出之前,将集合裡的東西clear,然後置為null,再退出程式。

單例類造成的記憶體洩漏

單例的靜态特性導緻其生命周期同應用一樣長。

解決方法:

将該屬性的引用方式改為弱引用;

如果傳入Context,使用ApplicationContext;

public class AppManager {

private static AppManager instance;

private Context context;

private AppManager(Context context) {

this.context = context;

}

public static AppManager getInstance(Context context) {

if (instance == null) {

instance = new AppManager(context);

}

return instance;

}

}

// **************************

private AppManager(Context context) {

this.context = context.getAppcalition();

}

// 或

WeakReference activityReference = new WeakReference(this);

Context context = activityReference.get();

if(context != null){

AppManager.getInstance(context);

// ...

}

private static ScrollHelper mInstance;

private ScrollHelper() {

}

public static ScrollHelper getInstance() {

if (mInstance == null) {

synchronized (ScrollHelper.class) {

if (mInstance == null) {

mInstance = new ScrollHelper();

}

}

}

return mInstance;

}

private View mScrolledView = null;

public void setScrolledView(View scrolledView) {

mScrolledView = scrolledView;

}

private WeakReference mScrolledViewWeakRef = null;

public void setScrolledView(View scrolledView) {

mScrolledViewWeakRef = new WeakReference(scrolledView);

}

非靜态或匿名内部類造成的記憶體洩漏

在Java中,非靜态内部類 和 匿名類 都會潛在的引用它們所屬的外部類,但是,靜态内部類卻不會。如果這個非靜态内部類執行個體做了一些耗時的操作,就會造成外圍對象不會被回收,進而導緻記憶體洩漏。

解決方案:

将内部類變成靜态内部類;

如果有強引用Activity中的屬性,則将該屬性的引用方式改為弱引用;

在業務允許的情況下,當Activity執行onDestory時,結束這些耗時任務;

public class LeakAct extends Activity {

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.aty_leak);

test();

}

//這兒發生洩漏

public void test() {

new Thread(new Runnable() {

@Override

public void run() {

while (true) {

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

}).start();

}

}

//加上static,變成靜态匿名内部類

public static void test() {

new Thread(new Runnable() {

@Override

public void run() {

while (true) {

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

}).start();

}

// 如果是内部類,靜态化内部類

static class MyThread extends Thread {

@Override

public void run() {

// ...

}

}

靜态内部類持有外部引用造成的記憶體洩漏

雖然靜态内部類的生命周期和外部類無關,但是如果在内部類中想要引入外部成員變量的話,這個成員變量必須是靜态的了,這也可能導緻記憶體洩露。

解決方法: 使用弱引用。

public class MainActivity extends Activity {

private static WeakReference activityReference;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

activityReference = new WeakReference(this);;

}

private static class MyThread extends Thread {

@Override

public void run() {

// 耗時操作

MainActivity activity = activityReference.get();

if(activity != null){

activity...

}

}

}

}

Handle造成的記憶體洩漏

當Handler中有延遲的的任務或是等待執行的任務隊列過長,由于消息持有對Handler的引用,而Handler又持有對其外部類的潛在引用,這條引用關系會一直保持到消息得到處理,而導緻了Activity無法被垃圾回收器回收,而導緻了記憶體洩露。

解決方案:

可以把Handler類放在單獨的類檔案中,或者使用靜态内部類便可以避免洩露;

如果想在Handler内部去調用所在的Activity,那麼可以在handler内部使用弱引用的方式去指向所在Activity。使用Static + WeakReference的方式來達到斷開Handler與Activity之間存在引用關系的目的。

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);

// Post a message and delay its execution for 10 minutes.

mLeakyHandler.postDelayed(new Runnable() {

@Override

public void run() { }

}, 1000 * 60 * 10);

// Go back to the previous Activity.

finish();

}

}

public class MainActivity extends Activity {

private final MyHandler mHandler = new MyHandler(this);

private static final Runnable mRunnable = new Runnable() {

@Override

public void run() { }

};

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

mHandler.postDelayed(mRunnable, 1000 * 60 * 10);

finish();

}

private static class MyHandler extends Handler {

private final WeakReference mActivityReference;

public MyHandler(MainActivity activity) {

mActivityReference = new WeakReference(activity);

}

@Override

public void handleMessage(Message msg) {

MainActivity activity = mActivityReference.get();

if (activity != null) {

// ...

}

}

}

}

static 修飾activity或間接修飾activity context

由于靜态變量和應用存活時間是相同的,直接或間接修飾了activity很榮耀造成記憶體洩漏問題。

解決方案:

不使用static修飾

在适當的地方将其置空

弱引用

Activity Context 的不正确使用造成的記憶體洩漏

在Android應用程式中通常可以使用兩種Context對象:Activity和Application。當類或方法需要Context對象的時候常見的做法是使用第一個作為Context參數。這樣就意味着View對象對整個Activity保持引用,是以也就保持對Activty的所有的引用。

解決方案:

使用Application Context代替Activity Context,因為Application Context會随着應用程式的存在而存在,而不依賴于activity的生命周期;

對Context的引用不要超過它本身的生命周期,慎重的對Context使用“static”關鍵字。Context裡如果有線程,一定要在onDestroy()裡及時停掉。

注冊監聽器造成的記憶體洩漏

系統服務可以通過Context.getSystemService 擷取,它們負責執行某些背景任務,或者為硬體通路提供接口。如果Context 對象想要在服務内部的事件發生時被通知,那就需要把自己注冊到服務的監聽器中。然而,這會讓服務持有Activity 的引用,如果在Activity onDestory時沒有釋放掉引用就會記憶體洩漏。

解決方案:

使用ApplicationContext代替ActivityContext;

在Activity執行onDestory時,調用反注冊;

mSensorManager = (SensorManager) this.getSystemService(Context.SENSOR_SERVICE);

// 解決

mSensorManager = (SensorManager) getApplicationContext().getSystemService(Context.SENSOR_SERVICE);

資源性對象造成的記憶體洩漏

Cursor,Stream沒有close,View沒有recyle都有可能造成記憶體洩漏。

在不使用他們時,應該及時關閉他們,以便它們的緩沖及時回收記憶體。

如果我們僅僅是把它的引用設定為null,而不關閉它們,往往會造成記憶體洩漏。

雖然系統在回收它時也會關閉它,但是這樣的效率太低了。

總結

對 Activity 等元件的引用應該控制在 Activity 的生命周期之内; 如果不能就考慮使用 getApplicationContext 或者 getApplication,以避免 Activity 被外部長生命周期的對象引用而洩露。

盡量不要在靜态變量或者靜态内部類中使用非靜态外部成員變量(包括context ),即使要使用,也要考慮适時把外部成員變量置空;也可以在内部類中使用弱引用來引用外部類的變量。

對于生命周期比Activity長的内部類對象,并且内部類中使用了外部類的成員變量,可以這樣做避免記憶體洩漏:

将内部類改為靜态内部類

靜态内部類中使用弱引用來引用外部類的成員變量

Handler 的持有的引用對象最好使用弱引用,資源釋放時也可以清空 Handler 裡面的消息。比如在 Activity onStop 或者 onDestroy 的時候,取消掉該 Handler 對象的 Message和 Runnable.

在 Java 的實作過程中,也要考慮其對象釋放,最好的方法是在不使用某對象時,顯式地将此對象指派為 null,比如使用完Bitmap 後先調用 recycle(),再賦為null,清空對圖檔等資源有直接引用或者間接引用的數組(使用 array.clear() ; array = null)等,最好遵循誰建立誰釋放的原則。

正确關閉資源,對于使用了BraodcastReceiver,ContentObserver,File,遊标 Cursor,Stream,Bitmap等資源的使用,應該在Activity銷毀時及時關閉或者登出。

保持對對象生命周期的敏感,特别注意單例、靜态對象、全局性集合等的生命周期。

參考: