記憶體洩漏,簡單而言就是該被釋放的對象沒有被釋放掉,一直被某個或某些執行個體引用,導緻無法被GC回收。
Java的記憶體配置設定政策
Java程式運作時,記憶體配置設定政策有三種:靜态配置設定、棧配置設定、堆配置設定。所對應的記憶體空間即為:靜态存儲區(方法區)、棧區、堆區。
靜态儲存區:編譯時就配置設定好,在程式整個運作期間都存在。它主要存放靜态資料和常量;
棧區:當方法執行時,會在棧區記憶體中建立方法體内部的局部變量,方法結束後自動釋放記憶體;
堆區:通常存放 new 出來的對象。由 Java 垃圾回收器回收。
局部變量的基本資料和引用存儲在棧中,引用的實體存儲在堆中。
因為他們屬于方法中的變量,生命周期随方法而結束。
成員變量全部存儲在堆中(包括基本資料、引用和引用的實體對象)。
因為他們屬于類,類對象被new的時候,會存儲在堆中。
Java中如何管理記憶體
記憶體配置設定:程式控制,new 對象申請記憶體空間(基本類型除外),所有對象都在堆中配置設定空間。
垃圾回收:GC完成記憶體的釋放。

Java記憶體管理
Java使用有向圖的方式進行記憶體管理,可以消除引用循環的問題。如果某個對象與這個根頂點不可達,那麼可以認為這個(這些)對象不再被引用,可以被 GC 回收。
還有一個常見的記憶體管理技術就是使用引用計數器。雖然執行效率高,但是精度低(很難處理循環引用問題。)
Java中的記憶體洩漏
這些對象是可達的。
這些對象是無用的。
即因為可達而不會被GC回收,但是又無用占據了記憶體,是以造成了記憶體洩漏。

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銷毀時及時關閉或者登出。
保持對對象生命周期的敏感,特别注意單例、靜态對象、全局性集合等的生命周期。
參考: