記憶體洩漏—出現情況,非靜態內部類對外部類引用持有的洩漏復現
阿新 • • 發佈:2018-12-26
前言
- 本文為製造一個”非靜態內部類對外部類的引用持有”洩漏並對其結果進行觀察作為學習使用,手段是製造洩漏,目的是瞭解洩漏產生的原因並未解決提供一種思路。
- 本文基於的思想是:2個Activity,其中一個Activity的內部類被外部引用掛住,導致該Acitvity無法正常回收。
Code
倆個Activity,一個SplashActivity,一個LeakActivity。操作路徑是從Splash跳到LeakActivity。往返5次之後,手動Gc。
SplashActivity
package zj.com;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import zj.com.rxjava_operators.R;
public class SplashActivity extends AppCompatActivity {
private LeakActivity.TestResource testResource;
private LeakActivity.TestResource testResource2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash);
findViewById(R.id.leakgo).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(SplashActivity.this, LeakActivity.class));
if (null == testResource)
testResource = new LeakActivity().new TestResource(1, "張三");
if (null == testResource2)
testResource2 = new LeakActivity().new TestResource(2, "李四");
}
});
}
}
LeakActivity
package zj.com;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import zj.com.rxjava_operators.R;
public class LeakActivity extends AppCompatActivity {
private TestResource mResource = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_leak);
mResource = new TestResource();
//...
}
class TestResource {
public TestResource() {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
// //...
int age;
String name;
public TestResource(int age, String name) {
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
程式碼分析
- 可以看到,整個記憶體洩漏是由於LeakActivity的”非靜態內部類”引起的。
- 你也注意到了,整個內部類TestResource,看起來挺彆扭的,空參構造內還開個執行緒休息10秒,我為啥要這麼寫?
- 我測試時,遇見倆種情況使這個記憶體洩漏無法復現,(1)當我空參構造休眠1秒時,(2)當我的TestResource內部類裡面啥都沒寫時。為什麼?具體太細我也說不上,這個可能要從GC的演算法判斷和回收的機制說起,這裡我先TODO,簡單分析下,我覺得應該是JVM一個內部的機制(據說是指令優化?),使得GC回收監測到上述倆種情況就會將這類物件去回收掉,從而無法引起記憶體洩漏,但是值得注意的一點是,當我在空參構造內睡1秒而非是10秒時,GC這時會去回收這個物件,而10秒時候則不會去回收,我覺得應該是GC監測到了該內部類物件還有未執行完畢的任務時,就不會去回收。
- 再看SplashActivity,每次點選的時候,我都會建立倆個TestResource的物件,這倆個物件主要是讓SplashActivity這個類從外部的因素去引用TestResource這個引用,從而引起記憶體洩漏。
總結
總結下,如果我們想要製造有內部類引起的記憶體洩漏:
- 那麼這個內部類一定不能為空類
- 在沒有外部影響的情況下,比如我在Splash內對Leak的內部類物件進行引用時,我們這時候就要讓Leak.TestResource擁有足夠的處理時間。
- 在有外部因素的影響下,這時候我們則可以不必考慮讓TestResouce去長時間處理一個任務也可以復現記憶體洩漏。