轉載請標明出處:https://www.cnblogs.com/tangZH/p/10955429.html
泄漏,泄漏,漏~
內存泄漏怎么破,什么是內存泄漏?與內存溢出有什么區別?
內存泄漏(Memory Leak):是指程序中己動態分配的堆內存由於某種原因程序未釋放或無法釋放,造成系統內存的浪費,導致程序運行速度減慢甚至系統崩潰等嚴重后果。
內存溢出(out of memory):是指程序在申請內存時,沒有足夠的內存空間供其使用,出現out of memory;
內存泄漏不一定會引起奔潰,但是內存溢出一定會
Java里頭有GC垃圾回收機制,他是怎么判斷該不該回收呢?
Q1:某對象沒有任何引用的時候才進行回收?
A:不。無法往上追溯到GCroot引用點的才回收。
Q2:某對象被別的對象引用就不能進行回收?
A:不。軟引用,弱引用,虛引用都可以
哪些可以作為GCroot引用點:
- Javastack中引用的對象
- 方法區中靜態引用指向的對象
- 方法區中常量引用指向的對象
- Native方法中JNI引用指向的對象
- Thread-活着的線程
adb命令驗證是否存在內存泄漏:
1、打開要測試的apk,然后返回退出到主界面
2、AS的Terminal中輸入命令:
adb shell dumpsys meminfo com.status.mattest -d
com.status.mattest為包名
然后便可以看到內存的一些情況
拉到下面可以看到:
我們退出APK之后,對象本應該都被回收,然而這里可以看到,還有view以及Activity占用着內存,由此可以知道內存泄漏了。
我們點擊AS上的按鈕,進行分析。
運行apk后會出現該界面:
我們點擊MEMORY進入內存分析界面:
這時候我們需要進行剛剛的操作,打開apk,然后返回鍵退出,然后點擊上圖中垃圾桶形狀的圖標進行垃圾回收,多點幾次,直到內存沒有什么變化了。
然后點擊上圖中類似於下載按鈕的圖標獲取內存快照。
獲取完之后,左邊會出現下面這圖,Head Dump便是獲取內存快照后出現的,我們可以點擊紅圈中的圖形進行保存
跟着出現的還有這個窗口。
我們可以通過包來分類查看自己的項目.
Shallow Heap(淺堆) 表示該對象自身占用的堆內存,不包括它引用的對象。
針對非數組類型的對象,它的大小就是對象與它所有的成員變量大小的總和。
Retained Heap(深堆) 表示當前對象大小+當前對象可直接或間接引用到的對象的大小總和。
換句話說,Retained Size就是當前對象被GC后,從Heap上總共能釋放掉的內存。
不過,釋放的時候還要排除被GC Roots直接或間接引用的對象。他們暫時不會被被當做Garbage。
從圖中可以看出,出現了內存泄漏的是CustomUtils,MainActivity,MainActivity$1代表MaiActivity里面的一個方法。
點擊MainActivity,可以看到這么對東西,眼花繚亂,我們根本不知道是哪個引起了內存泄漏。那咋辦。鋪墊結束,mat該上場了。
MAT
下載mat https://www.eclipse.org/mat/downloads.php
安裝步驟很簡單。
打開MAT后,點擊File -> Open Heap Dump 打開剛剛保存的內存快照,會發現打不開。
因為我們保存的內存快照是不能直接在MAT上打開的,需要進行轉化。
在AS的Terminal窗口輸入:hprof-conv -z A.hprof B.hprof
A.hprof為剛剛保存后的快照文件,B.hprof為轉換后的文件,也就是我們要在MAT上打開的文件
然后我們在MAT上將其打開。
點擊Finish
點擊Histogram
可以通過包名來分類查看
從下圖中可以看到引起內存泄漏的類,Objects那一列不為0的就是,與我們之前看到的一樣。
MainActivity右鍵
選擇圖中的選項,意思是過濾掉虛引用,軟引用,弱引用
之后可以看到引用的路徑,CustomUtils里面的instance引用了MainActivity的context,導致了MainActivity內存泄漏。
打開代碼看一下
package com.status.mattest; import android.content.Context; public class CustomUtils { private static CustomUtils instance; private CustomUtils(Context context) { this.mContext = context; } private Context mContext; public static CustomUtils getInstance(Context context) { if (instance == null) { instance = new CustomUtils(context); } return instance; } }
MainActivity中:
package com.status.mattest; import android.content.Intent; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.TextView; public class MainActivity extends AppCompatActivity { private TextView textView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); CustomUtils customUtils = CustomUtils.getInstance(this); textView = findViewById(R.id.tv); textView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { startActivity(new Intent(MainActivity.this, Test1Activity.class)); } }); } }
MainActivity中調用了單例CustomUtils,並且把context傳了進去,而我們知道instance作為靜態對象,是GCroot引用點,所以activity關閉了它也沒法被回收,那么最為被instance引用的Activity自然也無法被回收,所以導致了內存泄漏。
解決:
把單例模式里面的context換為全局application的context,也就是說單例里面的context的周期應該與進程一樣,而不能夠與應用一樣。當我們關閉應用的時候,進程還在,並沒有被殺死。