[轉]深刻Android內存泄露

深刻內存泄露java

Android應用的內存泄露,其實就是java虛擬機的堆內存泄漏.
固然,當應用有ndk,jni時,沒有及時free,本地堆也會出現內存泄漏.
本文只是針對JVM內存泄漏應用,進行闡述分析.android

1.知識儲備git

1.Java內存模型github

 

                    相關內存對象模型,參照博客精講Java內存模型數據庫

1) 寄存器(register)。這是最快的保存區域,這是主要因爲它位於處理器內部。然而,寄存器的數量十分有限,因此寄存器是須要由編譯器分配的。咱們對此沒有直接的控制權,也不可能在本身的程序裏找到寄存器存在的任何蹤影。安全

(2) 堆棧(stack)。在執行函數(方法)時,函數一些內部變量的存儲均可以放在棧上面建立,函數執行結束的時候這些存儲單元就會自動被釋放掉。位於通用RAM(隨機訪問存儲器)中。可經過它的「堆棧指針」 得到處理的直接支持。堆棧指針若向下移,會建立新的內存;若向上移,則會釋放那些內存。這是一種特別快、特別有效的數據保存方式,僅次於寄存器。網絡

(3) 堆(heap)。一種通用性的內存池(也在RAM區域),堆是不連續的內存區域,堆空間比較靈活也特別大。其中保存了Java對象(對象裏面的成員變量也在其中)。在堆裏分配存儲空間時會花掉更長的時間!也叫作動態內存分配。app

(4) 靜態存儲(static storage)。這兒的「靜態」(Static)是指「位於固定位置」(儘管也在RAM 裏)。程序運行期間,靜態存儲的數據將隨時等候調用。可用static關鍵字指出一個對象的特定元素是靜態的。但Java 對象自己永遠都不會置入靜態存儲空間,隨着JVM的生命週期結束而結束,即當app徹底退出,他纔會釋放。eclipse

(5) 常數存儲(constant storage)。常數值一般直接置於程序代碼內部。這樣作是安全的,由於它們永遠都不會改變。jvm

(6) 非RAM 存儲(non-storage-RAM)。若數據徹底獨立於一個程序以外,則程序不運行時仍可存在,並在程序的控制範圍以外。其中兩個最主要的例子即是「 流式對象」和「固定對象」 。對於流式對象,對象會變成字節流,一般會發給另外一臺機器。而對於固定對象,對象保存在磁盤中。

 

2.GC回收機制

引用自http://blog.csdn.net/jiafu1115/article/details/7024323

首先JVM是對堆進行回收操做.

1.JVM堆中分類

(1) 新域young generation:存儲全部新成生的對象

(2) 舊域old generation:新域中的對象,通過了必定次數的GC循環後,被移入舊域

(3) 永久域PermanentGeneration:存儲類和方法對象,從配置的角度看,這個域是獨立的,不包括在JVM堆內。默認爲4M。

2.Gc回收流程

 (1) 當eden滿了,觸發young GC;

 (2) young GC作2件事:一,去掉一部分沒用的object;二,把老的還被引用的object發到survior裏面,等下幾回GC之後,survivor再放到old裏面。

 (3) 當old滿了,觸發full GC。full GC很消耗內存,把old,young裏面大部分垃圾回收掉。這個時候用戶線程都會被block。

3.Gc回收總結

(1) JVM堆的大小決定了GC的運行時間。若是JVM堆的大小超過必定的限度,那麼GC的運行時間會很長。

(2) 對象生存的時間越長,GC須要的回收時間也越長,影響了回收速度。

(3) 大多數對象都是短命的,因此,若是能讓這些對象的生存期在GC的一次運行週期內,wonderful!

4.應用程序中,創建與釋放對象的速度決定了垃圾收集的頻率。

5.若是GC一次運行週期超過3-5秒,這會很影響應用程序的運行,若是能夠,應該減小JVM堆的大小了。

6.前輩經驗之談:一般狀況下,JVM堆的大小應爲物理內存的80%。


3.內存抖動

內存抖動這個術語可用於描述在極短期內分配給對象的過程.

例如,當你在循環語句中配置一系列臨時對象,或者在繪圖功能中配置大量對象時,這至關於內循環,當屏幕須要從新繪製或出現動畫時,你須要一幀幀使用這些功能,不過它會迅速增長你的堆的壓力。

Memory Monitor 內存抖動圖例:

 

2.內存泄漏對程序形成的影響

 (1) 直接:消耗內存,形成系應用自己的內存不足OutOfMemory.一個android應用程序,其實就是一個jvm虛擬機實例,而一個jvm的實例,在初始的時候,大小不等 16M,32M,64M(根據手機廠商和版本不一樣而不一樣),固然大小也能夠修改,參考修改博客。 

 (2) 間接:gc回收頻繁, 形成應用卡頓ANR.

 

  首先,當內存不足的時候,gc會主動回收沒用的內存.可是,內存回收也是須要時間的.

  上圖中,android在畫圖(播放視頻等)的時候,draw到界面的對象,和gc回收垃圾資源之間高頻率交替的執行.就會產生內存抖動.

  不少數據就會污染內存堆,立刻就會有許多GCs啓動,因爲這一額外的內存壓力,也會產生忽然增長的運算形成卡頓現象

  任何線程的任何操做都會須要暫停,等待GC操做完成以後,其餘操做纔可以繼續運行,因此垃圾回收運行的次數越少,對性能的影響就越少。

3. 內存泄露的緣由

  內存泄漏的本質:再也不用到的對象,被錯誤引用,而沒法被回收。未引用對象能夠被垃圾回收機制回收,而被引用對象不能被垃圾回收機制回收。 

 當內存不足,gc會回收垃圾內存, 垃圾內存是沒有別人使用的內存,好的內存而內存泄漏是正在被別人使用的的內存,不屬於垃圾內存堆引用內存泄漏(Heap leak)

(1) 靜態變量持有 已經沒有用的對象,致使對象沒法被回收.例如靜態集合類引發內存泄露

(2) 單例中持有的引用,當activity從新構建後,單例持有的是上一個activity實例.致使上一個沒法被回收.

(3) 事件監聽器和回調.若是一個類註冊了監聽器,但當該類再也不被使用後沒有註銷監聽器,可能會發生內存泄漏。

(4) 靜態內部類,持有對象.

(5) Handler 內存泄漏


系統資源泄露(Resource Leak)

主要指程序使用系統分配的資源好比 Bitmap,handle ,SOCKET等沒有使用相應的函數釋放掉,致使系統資源的浪費,嚴重可致使系統效能下降,系統運行不穩定。在try代碼塊裏建立鏈接,在finally裏釋放鏈接,就可以避免此類內存泄漏。

(1) bitmap資源未釋放

(2) IO流未關閉

(3) Cursor使用完後未釋放

(4) 各類鏈接(網絡,數據庫,socket等)

4.內存泄露的分析工具

在android studio 中有如下幾種工具,來進行內存泄漏優化分析(eclipse也有相似工具).

1.Memory Monitor 內存監視器. 

 

 

2.Dump java heap 

 

3.Android Device Monitor(eclipse系列工具類) 

 

4.第三方庫LeakCanary(極其簡單)

leakcanary的github地址: https://github.com/square/leakcanary  

5.內存泄露的實例解決方案

與其說解決內存泄漏,更應該說是 避免內存泄露 .由於內存泄漏一旦產生,即便須要重啓JVM,也就是重啓應用,內存從新開始計算。即便這樣,也無法解決。

1.單例形成的內存泄露 

/**
* Created by ccj on 2016/11/3.
*/

public class SingleExample {

private static SingleExample mExample;
private Context context;

private SingleExample(Context context) {
this.context = context;
}

/**
* 當MainActivity銷燬再重建後,此時的context,不會走 if (mExample == null) ,而是直接返回.
* 此時的Context 仍是上一個activity實例的Context,因此,上一個activity實例並未被釋放,形成內存泄漏
*
* 此時,只須要將application的上下文,做爲context便可解決問題
* @param context
* @return
*/
public static SingleExample getExampleInstance(Context context) {
if (mExample == null) {
mExample = new SingleExample(context);
}
return mExample;

}

}


2.非靜態內部類(匿名內部類) 的內存泄漏

非靜態內部類實例:

public class MainActivity extends Activity {

//會持有MainActivity實例。MainActivity.this.a
public void load(){
new Thread(new Runnable() {
@Override
public void run() {
while(true){
try {
int b=a;
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
解決方案:

將非靜態內部類修改成靜態內部類。由於靜態內部類不會隱士持有外部類

3.Handler 形成的內存泄漏

Java對引用的分類有 Strong reference, SoftReference, WeakReference, PhatomReference 四種。

在Android應用的開發中,爲了防止內存溢出,在處理一些佔用內存大並且聲明週期較長的對象時候,能夠儘可能應用軟引用和弱引用技術。

軟/弱引用能夠和一個引用隊列(ReferenceQueue)聯合使用,若是軟引用所引用的對象被垃圾回收器回收,Java虛擬機就會把這個軟引用加入到與之關聯的引用隊列中。利用這個隊列能夠得知被回收的軟/弱引用的對象列表,從而爲緩衝器清除已失效的軟/弱引用。

Handler 實例:

/* 在 Activity 中避免使用非靜態內部類,好比上面咱們將 Handler 聲明爲靜態的,
則其存活期跟 Activity 的生命週期就無關了。同時經過弱引用的方式引入 Activity,
避免直接將 Activity 做爲 context 傳進去,
推薦使用靜態內部類 + WeakReference 這種方式。每次使用前注意判空
*/
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() { /* ... */ }
}, 5000);
// Go back to the previous Activity.
finish();
}
}

解決方案

//改進機制

/*固然在Activity銷燬時候也應該取消相應的任務AsyncTask.cancel(),避免任務在後臺執行浪費資源*/。
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);
}
}


4.監聽器註冊形成的內存泄漏

在觀察者模式中, 有一個統一的觀察者collector集合,
事件監聽器和回調.若是一個類註冊了監聽器,但當該類再也不被使用後沒有註銷監聽器,可能會發生內存泄漏。例如,系統的傳感器sensor監聽器,
窗口改變監聽WindowFocusChangeListener等等.

監聽器實例:

系統級別的監聽,例如重力感應監聽sensorManager.registerListener(),若是不及時取消註冊,就會形成內存泄漏.

首先看Sensor中的官方註釋

/* Always make sure to disable sensors you don't need, especially when your activity is paused. Failing to do so can drain the battery in just a few hours. Note that the system will <i>not</i> disable sensors automatically when the screen turns off.*/
SensorManager sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ALL);
//監聽
sensorManager.registerListener(this,sensor,SensorManager.SENSOR_DELAY_FASTEST);

實例1解決方案:

protected void onPause() {
* super.onPause();
* mSensorManager.unregisterListener(this);
* }

觀察者模式實例2:

//本身的觀察者模式.
public class ListenerCollector {
static private WeakHashMap<View,MyView.MyListener> sListener = new WeakHashMap<>();
public void setsListener(View view, MyView.MyListener listener){ sListener.put(view,listener);}
//解決方案
public static void clearListeners(){
//hashmap移除監聽。
sListener.clear();
};
}


public class MyView extends View{
public MyView(Context context){
super(context);
init();
}

public interface MyListener{
public void myListenerCallback();
}

private void init(){
ListenerCollector collector = new ListenerCollector();
collector.setsListener(this,myListener);
}

private MyListener myListener = new MyListener() {
@Override
public void myListenerCallback() {
System.out.print("有被調用");
}
};

}

//activity調用處
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MyView myView = new MyView(this);
setContentView(myView);

}

實例2解決方案

@Override
protected void onStop() {
super.onStop();
ListenerCollector.clearListeners();
}

5.資源未關閉形成的內存泄漏

對於使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等資源的使用,應該在Activity銷燬時及時關閉或者註銷,不然這些資源將不會被回收,形成內存泄漏。

 

6.內存泄漏總結

一、對於生命週期比Activity長的對象若是須要應該使用ApplicationContext

二、在涉及到Context時先考慮ApplicationContext,固然它並非萬能的,對於有些地方則必須使用Activity的Context,對於Application,Service,Activity三者的Context的應用場景以下:
這裏寫圖片描述

     其中:NO1表示Application和Service能夠啓動一個Activity,不過須要建立一個新的task任務隊列。而對於Dialog而言,只有在Activity中才能建立

三、對於須要在靜態內部類中使用非靜態外部成員變量(如:Context、View ),能夠在靜態內部類中使用弱引用來引用外部類的變量來避免內存泄漏

四、對於生命週期比Activity長的內部類對象,而且內部類中使用了外部類的成員變量.將內部類改成靜態內部類,靜態內部類中使用弱引用來引用外部類的成員變量

五、對於再也不須要使用的對象,顯示的將其賦值爲null,好比使用完Bitmap後先調用recycle(),再賦爲null

六、保持對對象生命週期的敏感,特別注意單例、靜態對象、全局性集合等的生命週期。



原文連接:

https://blog.csdn.net/ccj659/article/details/53032683

相關文章
相關標籤/搜索