內存泄漏介紹
內存泄漏是針對與堆內存而言的。
Java的內存管理就是對象的分配和釋放。內存分配是由程序進行的,內存的釋放是由GC完成。GC只能回收那些無用,且不被其他對象引用的對象們佔用的空間。
從Main方法開始延伸,所有可以到達的對象都是有效對象,組成對象集合,這些不能被回收。其他的孤立對象則是GC回收的目標。
{
Object o = new Object();
}
局部變量的生命週期是在大括號結束。這時候,棧內的o被銷燬,則 new Object() 這個對象不在被引用,成爲孤立對象,會被GC回收。
所以,內存泄漏的根本原因是:堆內存中的長生命週期的對象持有短生命週期對象的強/軟引用,儘管短生命週期對象已經不再需要,但因爲長生命週期對象持有對他的引用而導致他不能被回收。
常見的內存泄漏
集合
集合如果只有添加方法,沒有刪除方法,內存會被大量佔用。如果集合是全局的,會造成內存只增不減。
單例
單例對象在被初始化後會在JVM整個生命週期中存在,如果單例持有了外部對象,會造成外部對象不能被回收,致使內存泄漏。
public class Test{
private static Test test;
private Context context;
private Test(Context context){
this.context = context;
}
public static Test getInstance(Context context){
if(test == null){
test = new Test(context);
}
return test;
}
}
如果傳入的context是一個activity,會使activity一直存在於內存中,不能被釋放。
Android中的各種組件
BroadCastReceiver,ContentObserver,Cursor等,在Regist後,記得在結束時調用unRegister或close(),並且,不要將Activity作爲成員變量使用,例如上面單例的使用。如果一定需要,則記得使用 WeakReference。
非靜態內部類
public class MainActivity extends AppCompatActivity {
private static TestResource mResource = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if(mResource == null){
mResource = new TestResource();
}
//...
}
class TestResource {
//...
}
}
外部類的靜態成員mResource的生命週期是整個程序的運行時間,而內部類的對象默認持有對外部類對象的一個引用,所以會導致Activity無法得到釋放。
可以將內部類定義爲靜態內部類,則可以將內部類和外部類的對象的關係切斷,只與類有關。
線程造成的內存泄漏
因爲線程的生命週期不可控,如果線程保存了對外部對象的引用,會造成外部對象的不可釋放。
Handler造成的內存泄漏
如果handler發送的message沒有被處理,那麼MessageQueue將一直保留對handler,message的引用,而且handler與Activity的生命週期並不一致,可能會導致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();
}
}
以上代碼,發送了延時的消息,這消息並未被處理時,該頁面退出會導致該頁面無法被回收。
注意事項
- 對activity的引用要考慮activity的生命週期,不過不能控制在其生命週期內,可以考慮用getApplicationContext(),避免activity長時間被佔用。
- 儘量不要在靜態內部類使用非靜態的成員變量,必須要用,也要在適當的時候將其置爲空;也可以在靜態內部類中使用弱引用來引用外部成員變量。
- Handler持有的引用對象最好爲弱引用,資源釋放的時候可以清空handler中的消息。
- 線程處理耗時操作,在頁面返回時即使取消。