一個對象只具備軟引用,在內存不足時
,這個對象纔會被 GC 回收。在 GC 時,若是一個對象只存在弱引用,那麼它將會被回收
。與 Android 中的差別:在 2.3 之後版本中,即便內存夠用,Android 系統會優先將 SoftReference 的對象提早回收掉, 其餘和 Java 中是同樣的。java
所以谷歌官方建議用
LruCache
(least recentlly use 最少最近使用算法)。會將內存控制在必定的大小內, 超出最大值時會自動回收, 這個最大值開發者本身定。算法
長生命週期的對象
持有短生命週期對象**強/軟引用**
,致使本應該被回收的短生命週期的對象卻沒法被正常回收。shell
例如在單例模式中,咱們經常在獲取單例對象時須要傳一個 Context 。單例對象是一個長生命週期的對象(應用程序結束時才終結),而若是咱們傳遞的是某一個 Activity 做爲 context,那麼這個 Activity 就會由於引用被持有而沒法銷燬,從而致使內存泄漏。數據庫
因爲單例模式的靜態特性,使得它的生命週期和咱們的應用同樣長,一不當心讓單例無限制的持有 Activity 的強引用就會致使內存泄漏。設計模式
public class BaseApplication extends Application{
private static ApplicationContext sContext;
@Override
public void onCreate(){
super.onCreate();
sContext = getApplicationContext();
}
public static Context getApplicationContext(){
return sContext;
}
}
複製代碼
因爲 Handler 屬於 TLS(Thread Local Storage)變量,致使它的生命週期和 Activity 不一致。所以經過 Handler 來更新 UI 通常很難保證跟 View 或者 Activity 的生命週期一致,故很容易致使沒法正確釋放。數組
例如:bash
public class HandlerBadActivity extends AppCompatActivity {
private final Handler handler = new Handler(){//非靜態內部類,持有外部類的強引用
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler_bad);
// 延遲 5min 發送一個消息
handler.postDelayed(new Runnable() {
//內部會將該 Runable 封裝爲一個 Message 對象,同時將 Message.target 賦值爲 handler
@Override
public void run() {
//do something
}
}, 1000 * 60 * 5);
this.finish();
}
}
複製代碼
上面的代碼中發送了了一個延時 5 分鐘執行的 Message,當該 Activity 退出的時候,延時任務(Message)還在主線程的 MessageQueue 中等待,此時的 Message 持有 Handler 的強引用
(建立時經過 Message.target 進行指定),而且因爲 Handler 是 HandlerBadActivity 的非靜態內部類
,因此 Handler 會持有一個指向 HandlerBadActivity 的強引用
,因此雖然此時 HandlerBadActivity 調用了 finish 也沒法進行內存回收,形成內存泄漏。架構
將 Handler 聲明爲靜態內部類
,可是要注意**若是用到 Context 等外部類的 非static 對象,仍是應該使用 ApplicationContext 或者經過弱引用來持有這些外部對象**
。app
public class HandlerGoodActivity extends AppCompatActivity {
private static final class MyHandler extends Handler{//聲明爲靜態內部類(避免持有外部類的強引用)
private final WeakReference<HandlerGoodActivity> mActivity;
public MyHandler(HandlerGoodActivity activity){
this.mActivity = new WeakReference<HandlerGoodActivity>(activity);//使用弱引用
}
@Override
public void handleMessage(Message msg) {
HandlerGoodActivity activity = mActivity.get();
if (activity == null || activity.isFinishing() || activity.isDestroyed()) {//判斷 activity 是否爲空,以及是否正在被銷燬、或者已經被銷燬
removeCallbacksAndMessages(null);
return;
}
// do something
}
}
private final MyHandler myHandler = new MyHandler(this);
}
複製代碼
static 修飾的變量位於內存的方法區,其生命週期與 App 的生命週期一致
。 這必然會致使一系列問題,若是你的 app 進程設計上是長駐內存的,那即便 app 切到後臺,這部份內存也不會被釋放。ide
不要在類初始化時初始化靜態成員,也就是能夠考慮懶加載。架構設計上要思考是否真的有必要這樣作,儘可能避免。若是架構須要這麼設計,那麼此對象的生命週期你有責任管理起來。
固然,Application 的 context 不是萬能的,因此也不能隨便亂用,對於有些地方則必須使用 Activity 的 Context,對於Application,Service,Activity三者的Context的應用場景以下:
功能 | Application | Service | Activity |
---|---|---|---|
Start an Activity | NO1 | NO1 | YES |
Show a Dialog | NO | NO | YES |
Layout Inflation | YES | YES | YES |
Start an Service | YES | YES | YES |
Bind an Service | YES | YES | YES |
Send a Broadcast | YES | YES | YES |
Register BroadcastReceiver | YES | YES | YES |
Load Resource Values | YES | YES | YES |
- NO1 表示 Application 和 Service 能夠啓動一個 Activity,不過
須要建立一個新的 task 任務隊列
。- 對於 Dialog 而言,只有在 Activity 中才能建立。
爲了方便咱們使用一些常見的系統服務,Activity 作了一些封裝。好比說,能夠經過 getPackageManager
在 Activtiy 中獲取 PackageManagerService
,可是,裏面實際上調用了 Activity 對應的 ContextImpl 中的 getPackageManager 方法
ContextWrapper#getPackageManager
@Override
public PackageManager getPackageManager() {
return mBase.getPackageManager();
}
複製代碼
ContextImpl#getPackageManager
@Override
public PackageManager getPackageManager() {
if (mPackageManager != null) {
return mPackageManager;
}
IPackageManager pm = ActivityThread.getPackageManager();
if (pm != null) {
// Doesn't matter if we make more than one instance. return (mPackageManager = new ApplicationPackageManager(this, pm));//建立 ApplicationPackageManager } return null; } 複製代碼
ApplicationPackageManager#ApplicationPackageManager
ApplicationPackageManager(ContextImpl context,
IPackageManager pm) {
mContext = context;//保存 ContextImpl 的強引用
mPM = pm;
}
private UserManagerService(Context context, PackageManagerService pm,
Object packagesLock, File dataDir) {
mContext = context;//持有外部 Context 引用
mPm = pm;
//代碼省略
}
複製代碼
PackageManagerService#PackageManagerService
public class PackageManagerService extends IPackageManager.Stub {
static UserManagerService sUserManager;//持有 UMS 靜態引用
public PackageManagerService(Context context, Installer installer,
boolean factoryTest, boolean onlyCore) {
sUserManager = new UserManagerService(context, this, mPackages);//初始化 UMS
}
}
複製代碼
遇到的內存泄漏問題是由於在 Activity 中調用了 getPackageManger 方法獲取 PMS ,該方法調用的是 ContextImpl,此時若是ContextImpl 中 PackageManager 爲 null,就會建立一個 PackageManger(ContextImpl 會將本身傳遞進去,而 ContextImpl 的 mOuterContext 爲 Activity),建立 PackageManager 實際上會建立 PackageManagerService(簡稱 PMS),而 PMS 的構造方法中會建立一個 UserManger(UserManger 初始化以後會持有 ContextImpl 的強引用)。
只要 PMS 的 class 未被銷燬,那麼就會一直引用着 UserManger ,進而致使其關聯到的資源沒法正常釋放。
將getPackageManager()
改成getApplication()#getPackageManager()
。這樣引用的就是 Application Context,而非 Activity 了。
由於使用非靜態內部類和匿名類都會默認持有外部類的引用,若是生命週期不一致,就會致使內存泄漏。
public class NestedClassLeakActivity extends AppCompatActivity {
class InnerClass {//非靜態內部類
}
private static InnerClass sInner;//指向非靜態內部類的靜態引用
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_nested_class);
if (sInner == null) {
sInner = new InnerClass();//建立非靜態內部類的實例
}
}
}
複製代碼
非靜態內部類默認會持有外部類的引用,而外部類中又有一個該非靜態內部類的靜態實例,該靜態實例的生命週期和應用的同樣長,而靜態實例又持有 Activity 的引用,所以致使 Activity 的內存資源不能正常回收。
將該內部類設爲靜態內部類 也能夠將該內部類抽取出來封裝成一個單例
咱們一般會把一些對象的引用加入到集合容器(好比ArrayList)中,當咱們再也不須要該對象時(一般會調用 remove 方法),並無把它的引用從集合中清理掉(其中的一種狀況就是 remove 方法沒有將再也不須要的引用賦值爲 null),下面以 ArrayList 的 remove 方法爲例
public E remove( int index) {
// 數組越界檢查
RangeCheck(index);
modCount++;
// 取出要刪除位置的元素,供返回使用
E oldValue = (E) elementData[index];
// 計算數組要複製的數量
int numMoved = size - index - 1;
// 數組複製,就是將index以後的元素往前移動一個位置
if (numMoved > 0)
System. arraycopy(elementData, index+1, elementData, index,
numMoved);
// 將數組最後一個元素置空(由於刪除了一個元素,而後index後面的元素都向前移動了,因此最後一個就沒用了),好讓gc儘快回收
elementData[--size ] = null; // Let gc do its work
return oldValue;
}
複製代碼
WebView 解析網頁時會申請Native堆內存
用於保存頁面元素,當頁面較複雜時會有很大的內存佔用。若是頁面包含圖片,內存佔用會更嚴重。而且打開新頁面時,爲了能快速回退,以前頁面佔用的內存也不會釋放
。有時瀏覽十幾個網頁,都會佔用幾百兆的內存。這樣加載網頁較多時,會致使系統不堪重負,最終強制關閉應用,也就是出現應用閃退或重啓。
因爲佔用的都是Native 堆內存
,因此實際佔用的內存大小不會顯示在經常使用的 DDMS Heap 工具中
( DMS Heap 工具看到的只是Java虛擬機分配的內存,即便Native堆內存已經佔用了幾百兆,這裏顯示的還只是幾兆或十幾兆)。只有使用 adb shell 中的一些命令好比 dumpsys meminfo 包名,或者在程序中使用 Debug.getNativeHeapSize()
才能看到 Native 堆內存信息。
聽說因爲 WebView 的一個 BUG,即便它所在的 Activity(或者Service) 結束也就是 onDestroy() 以後,或者直接調用 WebView.destroy()以後,它所佔用這些內存也不會被釋放。
把使用了 WebView 的 Activity (或者 Service) 放在單獨的進程裏。
使用 WebView 的頁面(Activity),在生命週期結束頁面退出(onDestory)的時候,主動調用WebView.onPause()==以及==WebView.destory()以便讓系統釋放 WebView 相關資源。
非靜態內部類的靜態實例
容易形成內存泄漏:即一個類中若是你不可以控制它其中內部類的生命週期(譬如Activity中的一些特殊Handler等),則儘可能使用靜態類和弱引用來處理(譬如ViewRoot的實現)。警戒線程未終止形成的內存泄露
;譬如在 Activity 中關聯了一個生命週期超過 Activity 的 Thread,在退出 Activity 時切記結束線程。
一個典型的例子就是 HandlerThread 的 run 方法。該方法在這裏是一個死循環,它不會本身結束,線程的生命週期超過了 Activity 生命週期,咱們必須手動在 Activity 的銷燬方法中中調用 thread.getLooper().quit() 纔不會泄露。
對象的註冊與反註冊沒有成對出現
形成的內存泄露;譬如註冊廣播接收器、註冊觀察者(典型的譬如數據庫的監聽)等。建立與關閉沒有成對出現形成的泄露
;譬如Cursor資源必須手動關閉,WebView必須手動銷燬,流等對象必須手動關閉等。