內存泄漏的代碼Demo
CommonUtils.java文件
public class CommonUtils {
private static CommonUtils instance;
private Context context;
private CommonUtils(Context context) {
this.context = context;
}
public static CommonUtils getInstance(Context context) {
if (instance == null) {
instance = new CommonUtils(context);
}
return instance;
}
}
MainActivity類
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private static Link linkInstance;
class Link {
public void dosomething() {
Log.i(TAG,"dosomething");
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (linkInstance == null) {
linkInstance = new Link();
linkInstance.dosomething();
}
CommonUtils.getInstance(this);
new MyThread().start();
}
class MyThread extends Thread {
@Override
public void run() {
while (true) {
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
生成hprof文件
在android profile中點擊 gc, 然後執行橫屏 ,豎屏切換,點擊dump java heap,再點擊Export capture to file,生成001.hprof.
再執行命令,生成001_mat.hprof給MAT來使用:
hprof-conv D:\001.hprof D:\001_mat.hprof
MAT的使用
下載一個 Mat工具
http://www.eclipse.org/mat/downloads.php
下載完之後就可以直接使用,雙擊MemoryAnalyzer.exe,然後直接把001_mat.hprof文件拖到 Mat 工具中。也可以File–Open Heap Dump,找到我們要使用的001_mat.hprof.
然後點擊 histogram:
效果如下:
我們重點關注自己寫的代碼,所以在最上面輸入此應用的包名,然後回車:
可以看到都是我們自己寫的代碼了,然後進行挨個分析:
分析
選擇第一個右鍵,List objects -> with incoming references ->回車
with incoming references : 表示該類被哪些內部對象 引用了
with outgoing references : 表示 該類持有了哪些外部對象的引用
回車 之後我們發現有三個 類如下圖。
我們選擇去掉弱引用,軟引用 所 引用的對象(右鍵—Path To GC Roots----exclude all phantom/ewak/soft etc.references):
顯示爲:
一下子就可以定位到問題的地方了.
MainActivity被三個對象引用了.
1.MyThread
2.CommonUtils
3.Link linkInstance
內存泄漏的代碼修改
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
//錯誤寫法
//private static Link linkInstance;
// 解決方法 不定義爲static類型
private Link linkInstance;
class Link {
public void dosomething() {
Log.i(TAG,"dosomething");
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (linkInstance == null) {
linkInstance = new Link();
linkInstance.dosomething();
}
// 解決方法 傳入應用的context
CommonUtils.getInstance(getApplicationContext());
//錯誤寫法
//CommonUtils.getInstance(this);
new MyThread().start();
}
//錯誤寫法
/*****
class MyThread extends Thread {
@Override
public void run() {
while (true) {
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
*****/
// 解決方法 讓MyThread爲靜態內部類,靜態內部類就不會持有外部類的引用
private static class MyThread extends Thread {
@Override
public void run() {
while (true) {
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
可能內存泄漏的代碼修改後,我們再來上面操作後的情況:
現在是不是沒有上面的幾個內存泄漏的問題了.
關於內存泄漏的一些知識點
爲什麼會有內存泄漏?
一個不會被使用的對象,因爲另一個正在使用的對象持有該對象的引用,導致它不能正常被回收,而停留在堆內存中,內存泄漏就產生了
常見的內存泄漏:
持有Context造成的內存泄漏
在Android中有兩種context對象:Activity和Application.當我們給一個類傳遞context的時候經常使用第一種,而這樣就導致了改類持有對Activity的全部引用,當Activity關閉的時候因爲被其他類持有,而導致無法正常被回收,而導致內存泄漏
解決方案:
在給其他給傳遞context的時候使用Application對象,這個對象的生命週期和共存亡,而不依賴activity的聲明週期. 而對context的引用不要超過他本身的生命週期,謹慎對context使用static關鍵字.
Handler造成的內存泄漏
public class SampleActivity extends Activity {
private final Handler mLeakyHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// ...
}
}
}
這樣來使用Handler會造成嚴重的內存泄漏.
假設Hanlder中有延遲的任務或是等在執行的任務隊列過長,由於消息隊列持有對handler的引用,而handler又持有activity的隱式引用,這個引用會保持到消息得到處理,而導致activity無法被垃圾回收器進行回收,而導致內存泄漏
解決方案:
可以把Handler放到單獨的類中,或者使用靜態的內部類(靜態內部類不會引用activity)避免泄漏
如果想要在handler內部去調用Activity中的資源,可以在Handler中使用弱引用的方式指向所在的Activity,使用static+WeakReference的方式斷開handler與activity的關係
public static class MyHandler extends Handler {
//聲明一個弱引用對象
WeakReference<MainActivity> mReference;
MyHandler(MainActivity activity) {
//在構造器中傳入Activity,創建弱引用對象
mReference = new WeakReference<MainActivity>(activity);
}
public void handleMessage(Message msg) {
//在使用activity之前先判空處理
if (mReference != null && mReference.get() != null) {
mReference.get().text.setText("hello word");
}
}
}
使用單利模式造成的內存泄漏
在我們使用單利模式的時候如果使用不當也會造成內存泄漏.因爲單利模式的靜態特徵使得單利模式的生命週期和應用一樣的長,這說明了當一個對象不需要使用了,而單利對象還存在該對象的引用,那麼這個對象就不能正常的被回收,就造成了內存泄漏
解決方案:
XXUtils.getInstance(this);
這句代碼默認傳入的是Activity的Context,而Activity是間接繼承自Context的,當Activity退出之後,單利對象還持有他的引用,所以在爲了避免傳Activity的Context,在單利中通過傳入的context獲取到全局的上下文對象,而不使用Activity的Context就解決了這個問題.
public class XXUtils {
private Context mContext;
private XXUtils(Context context) {
mContext = context.getApplicationContext();
}
private static XXUtils instance;
public static XXUtils getInstance(Context context) {
if (instance == null) {
synchronized (XXUtils.class) {
if (instance == null) {
instance = new XXUtils(context);
}
}
}
return instance;
}
}
非靜態內部類創建靜態實例造成的內存泄漏
在項目中我們爲了避免多次的初始化資源,常常會使用靜態對象去保存這些對象,這種情況也很容易引發內存泄漏.
why?
非靜態的內部類默認會持有外部類的引用
而我們又使用非靜態內部類創建了一個靜態的實例
該靜態實例的聲明週期和應用一樣長,這就導致了該靜態實例一直會持有該Activity的引用,導致Activity不能正常回收
解決方案:
將內部類修改成靜態的,這樣它對外部類就沒有引用
將該對象抽取出來封裝成一個單例.
private static TestResource mTestResource;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
private void initData() {
if (mTestResource == null) {
mTestResource = new TestResource();
}
}
//非靜態內部類默認會持有外部類的引用
//修改成就太之後正常被回收,是因爲靜態的內部類不會對Activity有引用
private static class TestResource {
}
線程造成的內存泄漏
當我們在使用線程的時候,一般都使用匿名內部類,而匿名內部類會對外部類持有默認的引用,當Acticity關閉之後如果現成中的任務還沒有執行完畢,就會導致Activity不能正常回收,造成內存泄漏
解決方案
創建一個靜態的類,實現Runnable方法,在使用的時候實例化他.
最終代碼:
private void loadData() {
new Thread(new MyThread()).start();
}
private static class MyThread implements Runnable {
public void run() {
SystemClock.sleep(20000);
}
}
資源未關閉造成的內存泄漏
對於使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等資源的代碼,應該在Activity銷燬時及時關閉或者註銷,否則這些資源將不會被回收,造成內存泄漏。
監聽器沒有註銷造成的內存泄漏
在Android程序裏面存在很多需要register與unregister的監聽器,我們需要確保及時unregister監聽器。
集合中的內存泄漏
我們通常把一些對象的引用加入到了集合容器(比如ArrayList)中,當我們不需要該對象時,
並沒有把它的引用從集合中清理掉,這樣這個集合就會越來越大。如果這個集合是static的話,那情況就更嚴重了。 所以要在退出程序之前,將集合裏的東西clear,然後置爲null,再退出程序。
參考資料
1.Android 性能優化 - 徹底解決內存泄漏
https://blog.csdn.net/wanghao200906/article/details/79305126
2.Android 內存泄露和性能檢測
https://blog.csdn.net/u012482178/article/details/78988176
3.Android Studio +MAT 分析內存泄漏實戰
https://blog.csdn.net/u012760183/article/details/52068490