Android
中,內存泄露的現象十分常見;而內存泄露致使的後果會使得應用Crash
ML (Memory Leak)
OOM
內存溢出 簡介: java
Java
存在垃圾回收機制(GC
),理應不存在內存泄露;出現內存泄露的緣由僅僅是外部人爲緣由 = 無心識地持有對象引用,使得 持有引用者的生命週期 > 被引用者的生命週期下面,將針對回收 進程、對象 、變量的內存分配 & 回收進行詳細講解git
由 ActivityManagerService
集中管理 全部進程的內存分配github
Application Framework
決定回收的進程類型 Android中的進程 是託管的;當進程空間緊張時,會 按進程優先級低->>高的順序 自動回收進程Android將進程分爲5個優先等級,具體以下:算法
Linux
內核真正回收具體進程
ActivityManagerService
對 全部進程進行評分(評分存放在變量adj
中)Linux
內核Linux
內核完成真正的內存回收此處僅總結流程,這其中的過程複雜,有興趣的讀者可研究系統源碼
ActivityManagerService.java
數據庫
Android
的對於對象、變量的內存策略同 Java
下面,將詳細講解內存分配 & 內存釋放策略性能優化
注:用1個實例講解 內存分配bash
public class Sample {
int s1 = 0;
Sample mSample1 = new Sample();
// 方法中的局部變量s二、mSample2存放在 棧內存
// 變量mSample2所指向的對象實例存放在 堆內存
// 該實例的成員變量s一、mSample1也存放在棧中
public void method() {
int s2 = 0;
Sample mSample2 = new Sample();
}
}
// 變量mSample3所指向的對象實例存放在堆內存
// 該實例的成員變量s一、mSample1也存放在堆內存中
Sample mSample3 = new Sample();
複製代碼
Java
垃圾回收器(GC
) / 幀棧 負責Java
垃圾回收器(GC
)因爲靜態分配不需釋放、棧式分配僅 經過幀棧自動出、入棧,較簡單,故不詳細描述多線程
Java
垃圾回收器(GC
)的內存釋放 = 垃圾回收算法,主要包括:Static
關鍵字修飾的成員變量內存泄露緣由 集合類 添加元素後,仍引用着 集合元素對象,致使該集合元素對象不可被回收,從而 致使內存泄漏eclipse
實例演示ide
// 經過 循環申請Object 對象 & 將申請的對象逐個放入到集合List
List<Object> objectList = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Object o = new Object();
objectList.add(o);
o = null;
}
// 雖釋放了集合元素引用的自己:o=null)
// 但集合List 仍然引用該對象,故垃圾回收器GC 依然不可回收該對象
複製代碼
因爲1個集合中有許多元素,故最簡單的方法 = 清空集合對象 & 設置爲
null
// 釋放objectList
objectList.clear();
objectList=null;
複製代碼
儲備知識 被 Static
關鍵字修飾的成員變量的生命週期 = 應用程序的生命週期
泄露緣由 若使被 Static
關鍵字修飾的成員變量 引用耗費資源過多的實例(如Context
),則容易出現該成員變量的生命週期 > 引用實例生命週期的狀況,當引用實例需結束生命週期銷燬時,會因靜態變量的持有而沒法被回收,從而出現內存泄露
實例講解
public class ClassName {
// 定義1個靜態變量
private static Context mContext;
//...
// 引用的是Activity的context
mContext = context;
// 當Activity需銷燬時,因爲mContext = 靜態 & 生命週期 = 應用程序的生命週期,故 Activity沒法被回收,從而出現內存泄露
}
複製代碼
Static
成員變量引用資源耗費過多的實例(如 Context
)若需引用
Context
,則儘可能使用Applicaiton
的Context
(WeakReference)
代替 強引用 持有實例儲備知識 單例模式 因爲其靜態特性,其生命週期的長度 = 應用程序的生命週期
泄露緣由 若1個對象已不需再使用 而單例對象還持有該對象的引用,那麼該對象將不能被正常回收 從而 致使內存泄漏
實例演示
// 建立單例時,需傳入一個Context
// 若傳入的是Activity的Context,此時單例 則持有該Activity的引用
// 因爲單例一直持有該Activity的引用(直到整個應用生命週期結束),即便該Activity退出,該Activity的內存也不會被回收
// 特別是一些龐大的Activity,此處很是容易致使OOM
public class SingleInstanceClass {
private static SingleInstanceClass instance;
private Context mContext;
private SingleInstanceClass(Context context) {
this.mContext = context; // 傳遞的是Activity的context
}
public SingleInstanceClass getInstance(Context context) {
if (instance == null) {
instance = new SingleInstanceClass(context);
}
return instance;
}
}
複製代碼
如上述實例,應傳遞
Application
的Context
,因Application
的生命週期 = 整個應用的生命週期
public class SingleInstanceClass {
private static SingleInstanceClass instance;
private Context mContext;
private SingleInstanceClass(Context context) {
this.mContext = context.getApplicationContext(); // 傳遞的是Application 的context
}
public SingleInstanceClass getInstance(Context context) {
if (instance == null) {
instance = new SingleInstanceClass(context);
}
return instance;
}
}
複製代碼
Handler
)即 外部類中 持有 非靜態內部類的靜態對象
// 背景:
a. 在啓動頻繁的Activity中,爲了不重複建立相同的數據資源,會在Activity內部建立一個非靜態內部類的單例
b. 每次啓動Activity時都會使用該單例的數據
public class TestActivity extends AppCompatActivity {
// 非靜態內部類的實例的引用
// 注:設置爲靜態
public static InnerClass innerClass = null;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 保證非靜態內部類的實例只有1個
if (innerClass == null)
innerClass = new InnerClass();
}
// 非靜態內部類的定義
private class InnerClass {
//...
}
}
// 形成內存泄露的緣由:
// a. 當TestActivity銷燬時,因非靜態內部類單例的引用(innerClass)的生命週期 = 應用App的生命週期、持有外部類TestActivity的引用
// b. 故 TestActivity沒法被GC回收,從而致使內存泄漏
複製代碼
若需使用
Context
,建議使用Application
的Context
- 多線程主要使用的是:
AsyncTask
、實現Runnable
接口 & 繼承Thread
類- 前3者內存泄露的原理相同,此處主要以繼承
Thread
類 爲例說明
/**
* 方式1:新建Thread子類(內部類)
*/
public class MainActivity extends AppCompatActivity {
public static final String TAG = "carson:";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 經過建立的內部類 實現多線程
new MyThread().start();
}
// 自定義的Thread子類
private class MyThread extends Thread{
@Override
public void run() {
try {
Thread.sleep(5000);
Log.d(TAG, "執行了多線程");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/**
* 方式2:匿名Thread內部類
*/
public class MainActivity extends AppCompatActivity {
public static final String TAG = "carson:";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 經過匿名內部類 實現多線程
new Thread() {
@Override
public void run() {
try {
Thread.sleep(5000);
Log.d(TAG, "執行了多線程");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
}
}
/**
* 分析:內存泄露緣由
*/
// 工做線程Thread類屬於非靜態內部類 / 匿名內部類,運行時默認持有外部類的引用
// 當工做線程運行時,若外部類MainActivity需銷燬
// 因爲此時工做線程類實例持有外部類的引用,將使得外部類沒法被垃圾回收器(GC)回收,從而形成 內存泄露
複製代碼
解決方案的思路 = 使得上述任1條件不成立 便可。
// 共有2個解決方案:靜態內部類 & 當外部類結束生命週期時,強制結束線程
// 具體描述以下
/**
* 解決方式1:靜態內部類
* 原理:靜態內部類 不默認持有外部類的引用,從而使得 「工做線程實例 持有 外部類引用」 的引用關係 不復存在
* 具體實現:將Thread的子類設置成 靜態內部類
*/
public class MainActivity extends AppCompatActivity {
public static final String TAG = "carson:";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 經過建立的內部類 實現多線程
new MyThread().start();
}
// 分析1:自定義Thread子類
// 設置爲:靜態內部類
private static class MyThread extends Thread{
@Override
public void run() {
try {
Thread.sleep(5000);
Log.d(TAG, "執行了多線程");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/**
* 解決方案2:當外部類結束生命週期時,強制結束線程
* 原理:使得 工做線程實例的生命週期 與 外部類的生命週期 同步
* 具體實現:當 外部類(此處以Activity爲例) 結束生命週期時(此時系統會調用onDestroy()),強制結束線程(調用stop())
*/
@Override
protected void onDestroy() {
super.onDestroy();
Thread.stop();
// 外部類Activity生命週期結束時,強制結束線程
}
複製代碼
具體請看文章:Android 內存泄露:詳解 Handler 內存泄露的緣由
泄露緣由 對於資源的使用(如 廣播BraodcastReceiver
、文件流File
、數據庫遊標Cursor
、圖片資源Bitmap
等),若在Activity
銷燬時無及時關閉 / 註銷這些資源,則這些資源將不會被回收,從而形成內存泄漏
解決方案 在Activity
銷燬時 及時關閉 / 註銷資源
// 對於 廣播BraodcastReceiver:註銷註冊
unregisterReceiver()
// 對於 文件流File:關閉流
InputStream / OutputStream.close()
// 對於數據庫遊標cursor:使用後關閉遊標
cursor.close()
// 對於 圖片資源Bitmap:Android分配給圖片的內存只有8M,若1個Bitmap對象佔內存較多,當它再也不被使用時,應調用recycle()回收此對象的像素所佔用的內存;最後再賦爲null
Bitmap.recycle();
Bitmap = null;
// 對於動畫(屬性動畫)
// 將動畫設置成無限循環播放repeatCount = 「infinite」後
// 在Activity退出時記得中止動畫
複製代碼
Context
、WebView
、Adapter
,具體介紹以下下面,我將用一張圖總結Android
中內存泄露的緣由 & 解決方案
MAT(Memory Analysis Tools)
Heap Viewer
Allocation Tracker
Android Studio 的 Memory Monitor
LeakCanary
Eclipse
的 Java Heap
內存分析工具 ->>下載地址經過分析
Java
進程的內存快照HPROF
分析,快速計算出在內存中對象佔用的大小,查看哪些對象不能被垃圾收集器回收 & 可經過視圖直觀地查看可能形成這種結果的對象
Java Heap
內存分析工具可查看 分別有哪些類型的數據在堆內存總 & 各類類型數據的佔比狀況
簡介:一個 Android Studio
自帶 的圖形化檢測內存工具
做用:跟蹤系統 / 應用的內存使用狀況。核心功能以下
square
出品的Android
開源庫 ->>下載地址本文 全面介紹了內存泄露的本質、緣由 & 解決方案,但願你們在開發時儘可能避免出現內存泄露
下一篇文章我將對講解Android
性能優化的相關知識,感興趣的同窗能夠繼續關注我的稀土掘金首頁哦