內(nèi)存溢出(Memory Overflow)和內(nèi)存泄露(Memory Leak)的區(qū)別
1)內(nèi)存泄漏例如:new了一塊內(nèi)存,但是沒有釋放,導(dǎo)致這塊內(nèi)存一直處于占用狀態(tài);內(nèi)存溢出例如:申請了10個字節(jié)的空間,但是寫入11或以上字節(jié)的數(shù)據(jù),就是溢出。
2)內(nèi)存泄露是導(dǎo)致內(nèi)存溢出的原因之一,內(nèi)存泄露積累起來將導(dǎo)致內(nèi)存溢出;內(nèi)存泄露可以通過完善代碼來避免;內(nèi)存溢出可以通過調(diào)整配置來減少發(fā)生頻率,但無法徹底避免。
內(nèi)存泄露
1.檢測工具
可以借助LeakCanary、MAT等工具來檢測應(yīng)用程序是否存在內(nèi)存泄漏:
1)MAT是一款強大的內(nèi)存分析工具,功能繁多而復(fù)雜;
2)LeakCanary則是由Square開源的一款輕量第三方內(nèi)存泄漏檢測工具,當(dāng)它檢測到程序中有內(nèi)存泄漏的產(chǎn)生時,它將以最直觀的方式告訴我們該內(nèi)存泄漏是由誰產(chǎn)生的和該內(nèi)存泄漏導(dǎo)致誰泄漏了而不能回收,供我們復(fù)查。
2.原因
當(dāng)一個對象已經(jīng)不需要再使用了,本該被回收時,而有另外一個正在使用的對象持有它的引用從而導(dǎo)致它不能被回收,這導(dǎo)致本該被回收的對象不能被回收而停留在堆內(nèi)存中,這就產(chǎn)生了內(nèi)存泄漏。
3.影響
內(nèi)存泄漏是造成應(yīng)用程序OOM的主要原因之一!我們知道Android系統(tǒng)為每個應(yīng)用程序分配的內(nèi)存有限,而當(dāng)一個應(yīng)用中產(chǎn)生的內(nèi)存泄漏比較多時,這就難免會導(dǎo)致應(yīng)用所需要的內(nèi)存超過這個系統(tǒng)分配的內(nèi)存限額,這就造成了內(nèi)存溢出而導(dǎo)致應(yīng)用Crash。
4.如何避免
1)盡早釋放無用對象的引用;
2)程序進行字符串處理時,盡量避免使用String,而應(yīng)使用StringBuffer。因為每一個String對象都會獨立占用內(nèi)存一塊區(qū)域。
3)盡量少用靜態(tài)變量。 因為靜態(tài)變量是全局的,GC不會回收。
4)避免集中創(chuàng)建對象尤其是大對象,如果可以的話盡量使用流操作。
5)盡量運用對象池技術(shù)以提高系統(tǒng)性能。生命周期長的對象擁有生命周期短的對象時容易引發(fā)內(nèi)存泄漏,例如大集合對象擁有大數(shù)據(jù)量的業(yè)務(wù)對象的時候,可以考慮分塊進行處理,然后解決一塊釋放一塊的策略。
6)不要在經(jīng)常調(diào)用的方法中創(chuàng)建對象,尤其是忌諱在循環(huán)中創(chuàng)建對象??梢赃m當(dāng)?shù)氖褂胔ashtable,vector創(chuàng)建一組對象容器,然后從容器中去取那些對象,而不用每次new之后又丟棄。
5.典型示例
1)循環(huán)創(chuàng)建對象
<pre>
Vector v=new Vector(10);
for (int i=1;i<100; i) {
Object o=new Object();
v.add(o);
o=null; // 此時o對象并未釋放,因為v對象仍持有它的引用
}
// 真正釋放需要:對象加入到Vector后,還必須從Vector中刪除,最簡單釋放方法就是將Vector對象設(shè)置為null
</pre>
2)單例造成的內(nèi)存泄露
由于單例的靜態(tài)特性使得單例的生命周期和應(yīng)用的生命周期一樣長,這就說明了如果一個對象已經(jīng)不需要使用了,而單例對象還持有該對象的引用,那么這個對象將不能被正?;厥?,這就導(dǎo)致了內(nèi)存泄漏。
以最基礎(chǔ)的單例模式為例:
<pre>
public class AppManager {
private static AppManager instance;
private Context context;
private AppManager(Context context) {
this.context = context; //此時傳進來的Context如果是Activity中的this,如果該Activity退出,但是單例中還是持有該Activity的引用,就會導(dǎo)致內(nèi)存泄漏
}
public static AppManager getInstance(Context context) {
if (instance != null) {
instance = new AppManager(context);
}
return instance;
}
}
</pre>
此時傳進來的Context如果是Activity中的this,如果該Activity退出,但是單例中還是持有該Activity的引用,就會導(dǎo)致內(nèi)存泄漏。正確的做法是:
<pre>
public class AppManager {
private static AppManager instance;
private Context context;
private AppManager(Context context) {
this.context = context.getApplicationContext(); //不管外面?zhèn)魅胧裁碈ontext,最終都會使用Applicaton的Context,而我們單例的生命周期和應(yīng)用的一樣長,這樣就防止了內(nèi)存泄漏。
}
public static AppManager getInstance(Context context) {
if (instance != null) {
instance = new AppManager(context);
}
return instance;
}
}
</pre>
不管外面?zhèn)魅胧裁碈ontext,最終都會使用Applicaton的Context,而我們單例的生命周期和應(yīng)用的一樣長,這樣就防止了內(nèi)存泄漏。
3)非靜態(tài)內(nèi)部類創(chuàng)建靜態(tài)實例造成的內(nèi)存泄漏
有的時候我們可能會在啟動頻繁的Activity中,為了避免重復(fù)創(chuàng)建相同的數(shù)據(jù)資源,可能會出現(xiàn)這種寫法:
<pre>
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(); // 非靜態(tài)內(nèi)部類的靜態(tài)實例
}
//...
}
class TestResource { // 非靜態(tài)內(nèi)部類
//...
}
}
</pre>
每次啟動Activity時都會使用該單例的數(shù)據(jù),這樣雖然避免了資源的重復(fù)創(chuàng)建,不過這種寫法卻會造成內(nèi)存泄漏,
因為非靜態(tài)內(nèi)部類默認會持有外部類的引用,
而又使用了該非靜態(tài)內(nèi)部類創(chuàng)建了一個靜態(tài)的實例,該實例的生命周期和應(yīng)用的一樣長,這就導(dǎo)致了該靜態(tài)實例一直會持有該Activity的引用,導(dǎo)致Activity的內(nèi)存資源不能正?;厥铡?br>
正確的做法為:
將該內(nèi)部類設(shè)為靜態(tài)內(nèi)部類或?qū)⒃搩?nèi)部類抽取出來封裝成一個單例,注意如果需要使用Context,請使用ApplicationContext。
4)Handler造成的內(nèi)存泄漏
Handler的使用造成的內(nèi)存泄漏問題應(yīng)該說最為常見了,平時在處理網(wǎng)絡(luò)任務(wù)或者封裝一些請求回調(diào)等api都應(yīng)該會借助Handler來處理,例如一下對Handler的使用可能會造成內(nèi)存泄漏:
<pre>
public class MainActivity extends AppCompatActivity {
private Handler mHandler = new Handler() {//非靜態(tài)內(nèi)名內(nèi)部類
@Override
public void handleMessage(Message msg) {
//...
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
loadData();
}
private void loadData(){
//...request
Message message = Message.obtain();
mHandler.sendMessage(message);
}
}
</pre>
由于mHandler是Handler的非靜態(tài)匿名內(nèi)部類的實例,所以它持有外部類Activity的引用。
消息隊列是在一個Looper線程中不斷輪詢處理消息,那么當(dāng)這個Activity退出時消息隊列中還有未處理的消息或者正在處理消息,而消息隊列中的Message持有mHandler實例的引用,mHandler又持有Activity的引用,所以導(dǎo)致該Activity的內(nèi)存資源無法及時回收,引發(fā)內(nèi)存泄漏。
另一種做法:
<pre>
public class MainActivity extends AppCompatActivity {
private MyHandler mHandler = new MyHandler(this);
private TextView mTextView ;
private static class MyHandler extends Handler {// 聲明Handler的靜態(tài)內(nèi)部類
private WeakReference<context> reference;
public MyHandler(Context context) {
reference = new WeakReference<>(context);//對Handler持有的對象使用弱引用
}
@Override
public void handleMessage(Message msg) {
MainActivity activity = (MainActivity) reference.get();
if(activity != null){
activity.mTextView.setText("");
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = (TextView)findViewById(R.id.textview);
loadData();
}
private void loadData() {
//...request
Message message = Message.obtain();
mHandler.sendMessage(message);
}
}
</pre>
創(chuàng)建一個靜態(tài)Handler內(nèi)部類,然后對Handler持有的對象使用弱引用,這樣在回收時也可以回收Handler持有的對象,這樣雖然避免了Activity泄漏,不過Looper線程的消息隊列中還是可能會有待處理的消息,所以我們在Activity的Destroy時或者Stop時應(yīng)該移除消息隊列中的消息。
更準確的做法如下:
<pre>
public class MainActivity extends AppCompatActivity {
private MyHandler mHandler = new MyHandler(this);
private TextView mTextView ;
private static class MyHandler extends Handler {
private WeakReference<context> reference;
public MyHandler(Context context) {
reference = new WeakReference<>(context);
}
@Override
public void handleMessage(Message msg) {
MainActivity activity = (MainActivity) reference.get();
if(activity != null){
activity.mTextView.setText("");
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = (TextView)findViewById(R.id.textview);
loadData();
}
private void loadData() {
//...request
Message message = Message.obtain();
mHandler.sendMessage(message);
}
@Override
protected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);//移除消息隊列中所有消息和所有的Runnable
}
}
</pre>
使用mHandler.removeCallbacksAndMessages(null);是移除消息隊列中所有消息和所有的Runnable。當(dāng)然也可以使用mHandler.removeCallbacks();或mHandler.removeMessages();來移除指定的Runnable和Message。
5)線程造成的內(nèi)存泄漏
異步任務(wù)和Runnable都是一個匿名內(nèi)部類,因此它們對當(dāng)前Activity都有一個隱式引用。如果Activity在銷毀之前,任務(wù)還未完成,
那么將導(dǎo)致Activity的內(nèi)存資源無法回收,造成內(nèi)存泄漏。正確的做法還是使用靜態(tài)內(nèi)部類的方式,如下:
<pre>
static class MyAsyncTask extends AsyncTask<void, void,="" void=""> {
private WeakReference<context> weakReference;
public MyAsyncTask(Context context) {
weakReference = new WeakReference<>(context);
}
@Override
protected Void doInBackground(Void... params) {
SystemClock.sleep(10000);
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
MainActivity activity = (MainActivity) weakReference.get();
if (activity != null) {
//...
}
}
}
static class MyRunnable implements Runnable{
@Override
public void run() {
SystemClock.sleep(10000);
}
}
//——————
new Thread(new MyRunnable()).start();
new MyAsyncTask(this).execute();</context></void,>
</pre>
這樣就避免了Activity的內(nèi)存資源泄漏,當(dāng)然在Activity銷毀時候也應(yīng)該取消相應(yīng)的任務(wù)AsyncTask::cancel(),避免任務(wù)在后臺執(zhí)行浪費資源。
6)資源未關(guān)閉造成的內(nèi)存泄漏
對于使用了BraodcastReceiver,ContentObserver,F(xiàn)ile,Cursor,Stream,Bitmap等資源的使用,應(yīng)該在Activity銷毀時及時關(guān)閉或者注銷,否則這些資源將不會被回收,造成內(nèi)存泄漏。
6.一些建議
1)對于生命周期比Activity長的對象如果需要應(yīng)該使用ApplicationContext;
2)對于需要在靜態(tài)內(nèi)部類中使用非靜態(tài)外部成員變量(如:Context、View ),可以在靜態(tài)內(nèi)部類中使用弱引用來引用外部類的變量來避免內(nèi)存泄漏;
3)對于不再需要使用的對象,顯示的將其賦值為null,比如使用完Bitmap后先調(diào)用recycle(),再賦為null;
4)保持對對象生命周期的敏感,特別注意單例、靜態(tài)對象、全局性集合等的生命周期;
5)對于生命周期比Activity長的內(nèi)部類對象,并且內(nèi)部類中使用了外部類的成員變量,可以這樣做避免內(nèi)存泄漏:
將內(nèi)部類改為靜態(tài)內(nèi)部類
靜態(tài)內(nèi)部類中使用弱引用來引用外部類的成員變量;
6)在涉及到Context時先考慮ApplicationContext,當(dāng)然它并不是萬能的,對于有些地方則必須使用Activity的Context,對于Application,Service,Activity三者的Context的應(yīng)用場景如下:

其中:NO1表示Application和Service可以啟動一個Activity,不過需要創(chuàng)建一個新的task任務(wù)隊列。而對于Dialog而言,只有在Activity中才能創(chuàng)建。