如何在應用裏面避免內存泄露java
內存泄漏常常出現的例子程序員
C/C++ 本身去分配內存和釋放內存——手動管理malloc和free 算法
內存不在GC掌控以內了。編程
當一個對象已經不須要再使用了,本該被回收時,而有另一個正在使用的對象持有它的引用從而就致使對象不能被回收。這種致使了本該被回收的對象不能被回收而停留在堆內存中,就產生了內存泄漏緩存
瞭解java的GC內存回收機制:某對象再也不有任何的引用的時候纔會進行回收。性能優化
ArrayList<String> list = new Arraylist<String>();
複製代碼
(回到頂部) bash
靜態的存儲區:內存在程序編譯的時候就已經分配好,這塊的內存在程序整個運行期間都一直存在。 它主要存放靜態數據、全局的static數據和一些常量。app
在執行函數(方法)時,函數一些內部變量的存儲均可以放在棧上面建立,函數執行結束的時候這些存儲單元就會自動被釋放掉。 棧內存包括分配的運算速度很快,由於內置在處理器的裏面的。固然容量有限。ide
也叫作動態內存分配。有時候能夠用malloc或者new來申請分配一個內存。在C/C++可能須要本身負責釋放(java裏面直接依賴GC機制)。函數
在C/C++這裏是能夠本身掌控內存的,須要有很高的素養來解決內存的問題。java在這一塊貌似程序員沒有很好的方法本身去解決垃圾內存,須要的是編程的時候就要注意本身良好的編程習慣。
1.空間和大小: 堆是不連續的內存區域,堆空間比較靈活也特別大。 棧式一塊連續的內存區域,大小是有操做系統覺決定的。
2.效率: 堆管理很麻煩,頻繁地new/remove會形成大量的內存碎片,這樣就會慢慢致使效率低下。 對於棧的話,他先進後出,進出徹底不會產生碎片,運行效率高且穩定。
public class Main{
int a = 1;
Student s = new Student();
public void XXX(){
int b = 1;//棧裏面
Student s2 = new Student();
}
}
複製代碼
1.成員變量所有存儲在堆中(包括基本數據類型,引用及引用的對象實體)---由於他們屬於類,類對象最終仍是要被new出來的。
2.局部變量的基本數據類型和引用存儲於棧當中,引用的對象實體存儲在堆中。-----由於他們屬於方法當中的變量,生命週期會隨着方法一塊兒結束。
咱們所討論內存泄露,主要討論堆內存,他存放的就是引用指向的對象實體。
有時候確實會有一種狀況:當須要的時候能夠訪問,當不須要的時候能夠被回收也能夠被暫時保存以備重複使用。好比:ListView或者GridView、RecyclerView.
加載大量數據或者圖片的時候,圖片很是佔用內存,必定要管理好內存,否則很容易內存溢出。滑出去的圖片就回收,節省內存。看ListView的源碼——回收對象,還會重用ConvertView。若是用戶反覆滑動或者下面還有一樣的圖片,就會形成屢次重複IO(很耗時),那麼須要緩存---平衡好內存大小和IO,算法和一些特殊的java類。
算法:lrucache(最近最少使用先回收)
特殊的java類:利於回收,StrongReference,SoftReference,WeakReference,PhatomReference
類 | 類型 | 回收時機 | 使用 | 生命週期 |
---|---|---|---|---|
StrongReference | 強引用 | 從不回收 | 對象的通常保存 | JVM中止的時候纔會終止 |
SoftReference | 軟引用 | 當內存不足的時候 | SoftReference結合 ReferenceQueue構造有效期短 |
內存不足時終止 |
WeakReference | 弱引用 | 在垃圾回收的時候 | 同軟引用 | GC後終止 |
PhatomReference | 虛引用 | 在垃圾回收的時候 | 合ReferenceQueue來跟蹤對象被 垃圾回收期回收的活動 |
GC後終止 |
開發時,爲了防止內存溢出,處理一些比較佔用內存大而且生命週期長的對象的時候,能夠儘可能使用軟引用和弱引用。 軟引用比LRU算法更加任性,回收量是比較大的,你沒法控制回收哪些對象。
好比使用場景:默認頭像、默認圖標。 ListView或者GridView、RecyclerView要使用內存緩存+外部緩存(SD卡)
單例模式致使內存對象沒法釋放而致使內存泄露
MainActivity在內存當中泄露了。
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
CommUtil commUtil = CommUtil.getInstance(this);
}
}
public class CommUtil {
private static CommUtil instance;
private Context context;
private CommUtil(Context context){
this.context = context;
}
public static CommUtil getInstance(Context context){
if(instance == null){
instance = new CommUtil(context);
}
return instance;
}
}
複製代碼
這個故事告訴咱們能用Application的context就用Application的CommonUtil生命週期跟MainActivity不一致,而是跟Application進程同生同死。
旋轉3次:會在內存裏面開闢三個MainActivity 實際上3次以上都只會有2個MainActivity。當GC回收的時候會將除了第0個和最後這一個留着其餘的都會被回收。
優化兩個狀況: 1.主動;平時 2.被動,很卡的時候 出現問題的時候。
若是咱們不知道代碼內存泄露的狀況,如何判斷咱們的項目裏面有哪些是有內存泄露狀況的?
1.憑藉工具結合本身的經驗來判斷。 每每咱們的app在某個時候或者某個操做之後會出現很卡的現象。
1)判斷就是查看內存抖動狀況
Android Monitor MAT (對Eclipse插件使用的,也有獨立分析工具)
查找引用了該對象的外部對象有哪些, 而後一個一個去猜,查找可能內存泄露的嫌疑犯,依據:看(讀代碼和猜)他們的生命週期是否一致(能夠經過快照對比),若是生命週期一致了確定不是元兇。
排除一些容易被回收的(軟引用、虛引用、弱引用)
設置監聽很容易出現內存泄露
handler.post(callback)
onDestroy(){
handler.removeCallback();
}
複製代碼
每每作項目的時候狀況很是複雜,或者項目作得差很少了想起來要性能優化檢查下內存泄露。
如何找到項目中存在的內存泄露的這些地方呢?
最直觀的看內存增加狀況,知道該動做是否發生內存泄露。
動做發生以前:GC完後內存1.4M; 動做發生以後:GC完後內存1.6M
MAT分析heap的總內存佔用大小來初步判斷是否存在泄露。 Heap視圖中有一個Type叫作data object,即數據對象,也就是咱們的程序中大量存在的類類型的對象。在data object一行中有一列是「Total Size」,其值就是當前進程中全部Java數據對象的內存總量,通常狀況下,這個值的大小決定了是否會有內存泄漏。
咱們反覆執行某一個操做並同時執行GC排除能夠回收掉的內存,注意觀察data object的Total Size值,正常狀況下Total Size值都會穩定在一個有限的範圍內,也就是說因爲程序中的的代碼良好,沒有形成對象不被垃圾回收的狀況。
反之若是代碼中存在沒有釋放對象引用的狀況,隨着操做次數的增多Total Size的值會愈來愈大。 那麼這裏就已經初步判斷這個操做致使了內存泄露的狀況。
MAT對比操做先後的hprof來定位內存泄露是泄露了什麼數據對象。(這樣作能夠排除一些對象,不用後面去查看全部被引用的對象是不是嫌疑)
快速定位到操做先後所持有的對象哪些是增長了(GC後仍是比以前多出來的對象就多是泄露對象嫌疑犯)
技巧:Histogram中還能夠對對象進行Group,好比選擇Group By Package更方便查看本身Package中的對象信息。
(1)進入Histogram,過濾出某一個嫌疑對象類
(2)而後分析持有此類對象引用的外部對象(在該類上面點擊右鍵List Objects--->with incoming references)
(3)再過濾掉一些弱引用、軟引用、虛引用,由於它們早晚能夠被GC幹掉不屬於內存泄露(在類上面點擊右鍵Merge Shortest Paths to GC Roots--->exclude all phantom/weak/soft etc.references)
(4)逐個分析每一個對象的GC路徑是否正常,此時就要進入代碼分析此時這個對象的引用持有是否合理,這就要考經驗和體力了!
(好比上面的例子中:旋轉屏幕後MainActivity有兩個,確定MainActivity發生泄露了,那誰致使他泄露的呢?原來是咱們的CommonUtils類持有了旋轉以前的那個MainActivity,那是否合理?結合邏輯判斷固然不合理,由此找到內存泄露根源是CommonUtils類持有了該MainActivity實例形成的。怎麼解決?罪魁禍首找到了,怎麼解決應該不難了,不一樣狀況解決辦法不同,要靠你的智慧了。)
context.getapplictioncontext()能夠嗎? 能夠!!只要讓CommonUtils類不直接只有MainActivity的實例就能夠了。
通常我是最笨的方法解決
new出來對象,用完後把它 = null;這樣算不算優化
假如:
方法裏面定義的對象,要去管嗎?通常不須要管。
本身=null,要本身去控制全部對象的生命週期 判斷各類空指針,有點麻煩。
可是在不少時候去想到主動將對象置爲null是很好的習慣。
複製代碼
判斷一個應用裏面內存泄露避省得很好,怎麼看?
當app退出的時候,這個進程裏面全部的對象應該就都被回收了,尤爲是很容易被泄露的(View,Activity)是否還內存當中。
可讓app退出之後,查看系統該進程裏面的全部的View、Activity對象是否爲0.
使用AndroidStudio--AndroidMonitor--System Information--Memory Usage查看Objects裏面的views和Activity的數量是否爲0.
命令行模式:
進程中某些對象已經沒有使用價值了,可是他們卻還能夠直接或者間接地被引用到GC Root致使沒法回收。
當內存泄露過多的時候,再加上應用自己佔用的內存,日積月累最終就會致使內存溢出OOM.
當應用佔用的heap資源超過了Dalvik虛擬機分配的內存就會內存溢出。好比:加載大圖片。
當調用getInstance時,若是傳入的context是Activity的context。只要這個單利沒有被釋放,那麼這個 Activity也不會被釋放一直到進程退出纔會釋放。
public class CommUtil {
private static CommUtil instance;
private Context context;
private CommUtil(Context context){
this.context = context;
}
public static CommUtil getInstance(Context mcontext){
if(instance == null){
instance = new CommUtil(mcontext);
}
// else{
// instance.setContext(mcontext);
// }
return instance;
}
複製代碼
錯誤的示範:
public void loadData(){//隱式持有MainActivity實例。MainActivity.this.a
new Thread(new Runnable() {
@Override
public void run() {
while(true){
try {
//int b=a;
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
複製代碼
解決方案: 將非靜態內部類修改成靜態內部類。(靜態內部類不會隱式持有外部類)
當使用軟引用或者弱引用的時候,MainActivity難道很容易或者能夠被GC回收嗎?
GC回收的機制是什麼?
當MainActivity不被任何的對象引用。 雖然Handler裏面用的是軟引用/弱引用,可是並不意味着不存在其餘的對象引用該MainActivity。
我連MainActivity都被回收了,那他裏面的Handler還玩個屁。
例子1:
// tv.setOnClickListener();//監聽執行完回收對象
//add監聽,放到集合裏面
tv.getViewTreeObserver().addOnWindowFocusChangeListener(new ViewTreeObserver.OnWindowFocusChangeListener() {
@Override
public void onWindowFocusChanged(boolean b) {
//監聽view的加載,view加載出來的時候,計算他的寬高等。
//計算完後,必定要移除這個監聽
tv.getViewTreeObserver().removeOnWindowFocusChangeListener(this);
}
});
複製代碼
例子2:
SensorManager sensorManager = getSystemService(SENSOR_SERVICE);
Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ALL);
sensorManager.registerListener(this,sensor,SensorManager.SENSOR_DELAY_FASTEST);
//不須要用的時候記得移除監聽
sensorManager.unregisterListener(listener);
複製代碼
好比:BroadCastReceiver、Cursor、Bitmap、IO流、自定義屬性attribute attr.recycle()回收。
當不須要使用的時候,要記得及時釋放資源。不然就會內存泄露。
沒有在onDestroy中中止動畫,不然Activity就會變成泄露對象。 好比:輪播圖效果。