1. 程式人生 > >Android,誰動了我的記憶體(1)

Android,誰動了我的記憶體(1)

一、 Android的記憶體機制

    Android的程式由Java語言編寫,所以Android的記憶體管理與Java的記憶體管理相似。程式設計師通過new為物件分配記憶體,所有物件在java堆內分配空間;然而物件的釋放是由垃圾回收器來完成的。C/C++中的記憶體機制是“誰汙染,誰治理”,java的就比較人性化了,給我們請了一個專門的清潔工(GC)。

    那麼GC怎麼能夠確認某一個物件是不是已經被廢棄了呢?Java採用了有向圖的原理。Java將引用關係考慮為圖的有向邊,有向邊從引用者指向引用物件。執行緒物件可以作為有向圖的起始頂點,該圖就是從起始頂點開始的一棵樹,根頂點可以到達的物件都是有效物件,GC不會回收這些物件。如果某個物件 (連通子圖)與這個根頂點不可達(注意,該圖為有向圖),那麼我們認為這個(這些)物件不再被引用,可以被GC回收。

二、Android的記憶體溢位

    Android的記憶體溢位是如何發生的?

    Android的虛擬機器是基於暫存器的Dalvik,它的最大堆大小一般是16M,有的機器為24M。因此我們所能利用的記憶體空間是有限的。如果我們的記憶體佔用超過了一定的水平就會出現OutOfMemory的錯誤。

為什麼會出現記憶體不夠用的情況呢?我想原因主要有兩個:

  • 由於我們程式的失誤,長期保持某些資源(如Context)的引用,造成記憶體洩露,資源造成得不到釋放。
  • 儲存了多個耗用記憶體過大的物件(如Bitmap),造成記憶體超出限制。

三、萬惡的static

    static是Java中的一個關鍵字,當用它來修飾成員變數時,那麼該變數就屬於該類,而不是該類的例項。所以用static修飾的變數,它的生命週期是很長的,如果用它來引用一些資源耗費過多的例項(Context的情況最多),這時就要謹慎對待了。

  1. public class ClassName {  
  2.      private static Context mContext;  
  3.      //省略  
  4. }  

以上的程式碼是很危險的,如果將Activity賦值到麼mContext的話。那麼即使該Activity已經onDestroy,但是由於仍有物件儲存它的引用,因此該Activity依然不會被釋放。

    我們舉Android文件中的一個例子。

  1. private static Drawable sBackground;  
  2.  @Override  
  3.  protected void onCreate(Bundle state) {  
  4.    super
    .onCreate(state);  
  5.    TextView label = new TextView(this);  
  6.    label.setText("Leaks are bad");  
  7.    if (sBackground == null) {  
  8.      sBackground = getDrawable(R.drawable.large_bitmap);  
  9.    }  
  10.    label.setBackgroundDrawable(sBackground);  
  11.    setContentView(label);  
  12.  }  

    sBackground, 是一個靜態的變數,但是我們發現,我們並沒有顯式的儲存Contex的引用,但是,當Drawable與View連線之後,Drawable就將View設定為一個回撥,由於View中是包含Context的引用的,所以,實際上我們依然儲存了Context的引用。這個引用鏈如下:

    Drawable->TextView->Context

    所以,最終該Context也沒有得到釋放,發生了記憶體洩露。

    如何才能有效的避免這種引用的發生呢?

    第一,應該儘量避免static成員變數引用資源耗費過多的例項,比如Context。

    第二、Context儘量使用Application Context,因為Application的Context的生命週期比較長,引用它不會出現記憶體洩露的問題。

    第三、使用WeakReference代替強引用。比如可以使用WeakReference<Context> mContextRef;

    該部分的詳細內容也可以參考Android文件中Article部分。

四、都是執行緒惹的禍

    執行緒也是造成記憶體洩露的一個重要的源頭。執行緒產生記憶體洩露的主要原因在於執行緒生命週期的不可控。我們來考慮下面一段程式碼。

  1. public class MyActivity extends Activity {  
  2.     @Override  
  3.     public void onCreate(Bundle savedInstanceState) {  
  4.         super.onCreate(savedInstanceState);  
  5.         setContentView(R.layout.main);  
  6.         new MyThread().start();  
  7.     }  
  8.     private class MyThread extends Thread{  
  9.         @Override  
  10.         public void run() {  
  11.             super.run();  
  12.             //do somthing  
  13.         }  
  14.     }  
  15. }  

    這段程式碼很平常也很簡單,是我們經常使用的形式。我們思考一個問題:假設MyThread的run函式是一個很費時的操作,當我們開啟該執行緒後,將裝置的橫屏變為了豎屏,一般情況下當螢幕轉換時會重新建立Activity,按照我們的想法,老的Activity應該會被銷燬才對,然而事實上並非如此。

    由於我們的執行緒是Activity的內部類,所以MyThread中儲存了Activity的一個引用,當MyThread的run函式沒有結束時,MyThread是不會被銷燬的,因此它所引用的老的Activity也不會被銷燬,因此就出現了記憶體洩露的問題。

    有些人喜歡用Android提供的AsyncTask,但事實上AsyncTask的問題更加嚴重,Thread只有在run函式不結束時才出現這種記憶體洩露問題,然而AsyncTask內部的實現機制是運用了ThreadPoolExcutor,該類產生的Thread物件的生命週期是不確定的,是應用程式無法控制的,因此如果AsyncTask作為Activity的內部類,就更容易出現記憶體洩露的問題。

    這種執行緒導致的記憶體洩露問題應該如何解決呢?

    第一、將執行緒的內部類,改為靜態內部類。

    第二、線上程內部採用弱引用儲存Context引用。

    解決的模型如下:

  1. public abstract class WeakAsyncTask<Params, Progress, Result, WeakTarget> extends  
  2.         AsyncTask<Params, Progress, Result> {  
  3.     protected WeakReference<WeakTarget> mTarget;  
  4.     public WeakAsyncTask(WeakTarget target) {  
  5.         mTarget = new WeakReference<WeakTarget>(target);  
  6.     }  
  7.     /** {@inheritDoc} */  
  8.     @Override  
  9.     protected final void onPreExecute() {  
  10.         final WeakTarget target = mTarget.get();  
  11.         if (target != null) {  
  12.             this.onPreExecute(target);  
  13.         }  
  14.     }  
  15.     /** {@inheritDoc} */  
  16.     @Override  
  17.     protected final Result doInBackground(Params... params) {  
  18.         final WeakTarget target = mTarget.get();  
  19.         if (target != null) {  
  20.             return this.doInBackground(target, params);  
  21.         } else {  
  22.             return null;  
  23.         }  
  24.     }  
  25.     /** {@inheritDoc} */  
  26.     @Override  
  27.     protected final void onPostExecute(Result result) {  
  28.         final WeakTarget target = mTarget.get();  
  29.         if (target != null) {  
  30.             this.onPostExecute(target, result);  
  31.         }  
  32.     }  
  33.     protected void onPreExecute(WeakTarget target) {  
  34.         // No default action  
  35.     }  
  36.     protected abstract Result doInBackground(WeakTarget target, Params... params);  
  37.     protected void onPostExecute(WeakTarget target, Result result) {  
  38.         // No default action  
  39.     }  
  40. }  

    事實上,執行緒的問題並不僅僅在於記憶體洩露,還會帶來一些災難性的問題。由於本文討論的是記憶體問題,所以在此不做討論。

相關推薦

Android記憶體(1)

一、 Android的記憶體機制     Android的程式由Java語言編寫,所以Android的記憶體管理與Java的記憶體管理相似。程式設計師通過new為物件分配記憶體,所有物件在java堆內分配空間;然而物件的釋放是由垃圾回收器來完成的。C/C++中的記憶體機制

的乳酪的程式碼的“bug"

  "你的是我的,我的還是我的”經常在熱戀中的情侶中這樣說,但現實生活中還是要好好掌握自己的主動權。 自己的東西,始終是自己的,不允許任何人在不知情的情況下,進行隨意的支配,就算在親密的人,在不知情的情況下,觸犯到了彼此的隱私,心情也會不美麗的,久而久之,還會積累很多的矛盾。    

偶現的MissingServletRequestParameterException的引數?

概述 排查過程 結論 概述 最近遇到一個偶現的問題,在向服務端請求的時候,偶爾會出現異常,在請求中的query String 傳遞了引數,卻出現了異常MissingServletRequestParameterException 如下所示

深入理解PHP記憶體管理之記憶體

首先讓我們看一個問題: 如下程式碼的輸出, var_dump(memory_get_usage()); $a = "laruence"; var_dump(memory_get_usage()); unset($a); var_dump(memory_get_usage()); 輸出

的網絡資產》:4星。移動互聯時代的軟件使用技巧。

分享 常見 播放 4.4 權重 短信 排名 引擎 一道 本書作者自稱極客,我基本認可。全書講的是一些軟件使用的說明和技巧,許多技巧對IT從業人員來說也是比較新鮮的。 全書內容大致有以下三類:1:反欺詐知識與技巧;2:一些場景下的軟件推薦;3:軟件使用技巧。 總體評價

的特征?——sklearn特征轉換行為全記錄

blog selection clas 意義 print encoder 分享 steps 轉換函數 目錄 1 為什麽要記錄特征轉換行為?2 有哪些特征轉換的方式?3 特征轉換的組合4 sklearn源碼分析  4.1 一對一映射  4.2 一對多映射  4.3 多對多映

MySQL實戰 | 03 - 的資料:淺析MySQL的事務隔離級別

原文連結:這一次,帶你搞清楚MySQL的事務隔離級別! 使用過關係型資料庫的,應該都事務的概念有所瞭解,知道事務有 ACID 四個基本屬性:原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)和永續性(Durability),今天我們主要來理解一下事務的隔離性。

的Girl的窩

本文程式碼使用的語言為C#,請使用別的語言的同學自行轉換為自己習慣的語言 談到單例模式,首先要明確為什麼要使用單例模式? 目的:保證一個類僅有一個例項物件,並且該物件全域性共享 方法:構造私有化,屬性或方法公開化 1、最簡單的單例模式:餓漢式    由於在類被載入

不同人對BUG的反應,程式設計師:的程式碼?

Bug 是個很有趣的東西,有程式碼的地方就有它,不同人對待 Bug也有不同的反應,一起來看吧~ 程式設計師:誰動了我的程式碼? 這確實是一種似曾相識的感覺,我經過無數次的解釋都沒有人相信,但我還是要說一句:它原本不是這個樣子的。 不過,程式猿通常有著執著的精神,在夜深人靜

的 Token

這裡涉及到的系統是一個 7 年的遺留系統(技術棧是 .NET MVC2),即將被客戶淘汰。這篇博文的主題無關技術本身,文中談到的技術細節也不是什麼高大上的,更多的是想記錄因這件事情觸發的非技術思考。 早上7點45分來到公司,我坐在辦公桌旁邊開始考慮今天的工作事項。想到客戶一直抱怨的電子表單系統在產品環境上8

的乳酪?--java例項初始化的順序問題

故事背景 有一天,老鼠小白髮現了一個奇怪的問題,它的乳酪的生產日期被誰搞丟了,不知道乳酪是否過期,可怎麼吃呀?   讓我們來看看吧 import java.util.Date;public class Cheese { public static final Cheese chees

HashMap踩坑實錄——的乳酪

說到HashMap,hashCode 和 equals ,想必絕大多數人都不會陌生,然而你真的瞭解這它們的機制麼?本文將通過一個簡單的Demo還原我自己前不久在 HashMap 上導致的線上問題,看看我是如何跳進這個坑裡去的。 起因 在重構一段舊程式碼的時候發現有個 HashMap 的key物件沒有重寫 ha

的熱更新?MonoJITiOS

前言 由於匹夫本人是做遊戲開發工作的,所以平時也會加一些玩家的群。而一些困擾玩家的問題,同樣也困擾著我們這些手機遊戲開發者。這不最近匹夫看自己加的一些群,常常會有人問為啥這個遊戲一更新就要重新下載,而不能遊戲內更新呢?作為遊戲開發者,或者說Unity3D程式猿,我們都清楚Unity3D不支援熱更新,甚至於在

盜用的賬號簡直可怕!!!!!!

寫篇文章,紀念我被盜過的csdn賬號今天2018-06-14登入csdn部落格發現部落格被封!!詢問管理員,管理員說,部落格發了大量非法連結。回去查看回收站,看到如下類似文章,都是這種隨便截的標題,內容是一大堆連結!!!連結全是某交友網站。。。。幾秒鐘轉一篇,肯定不是手速的問

linux系統監控:記錄用戶操作軌跡過服務器

linux linux script linux安全 linux系統監控 甘兵 1、前言 我們在實際工作當中,都碰到過誤操作、誤刪除、誤修改過配置文件等等事件。對於沒有堡壘機的公司來說,要在linux系統上深究到底誰做過配置文件的修改、做過誤刪除是很頭疼的事情,特別是遇到刪庫跑路

無意中發現Markdown最終解放

align strong issues .cn 標題 ons 對齊方式 強制 arp 文件夾 概述 換行 刪除線 鏈接自己主動識別 表格 代碼塊高亮 定義列表 腳

C# 的代碼

也不會 main 更改 alt gpo 添加 reat 是我 目的 本文告訴大家一個特殊的做法,可以修改一個字符串常量 我們來寫一個簡單的程序,把一個常量字符串輸出 private const string str = "lindexi";

C# 的代碼 使用 Resharper 快速做適配器

params 比較 lin nbsp UNC nal set return 程序 本文告訴大家一個特殊的做法,可以修改一個字符串常量 我們來寫一個簡單的程序,把一個常量字符串輸出 private const string str = "lindex

Atitit hibernste5 註解方式開發總結 目錄 1. 映入hb5的jar 建立專案 1 1.1. 建表tab1 這裡使用sqlite資料庫 1 1.2. 建立對映實體類tab1

Atitit hibernste5  註解方式開發總結     目錄 1. 映入hb5的jar 建立專案 1 1.1. 建表tab1  ,這裡使用了sqlite資料庫 1 1.2. 建立對映實體類tab1  

的神經網路?(三)—— 啟用函式

誰擋了我的神經網路?(三)—— 啟用函式 這一系列文章介紹了在神經網路的設計和訓練過程中,可能提升網路效果的一些小技巧。前文介紹了在訓練過程中的一系列經驗,這篇文章將重點關注其中的啟用函式部分。更新於2018.11.1。 文章目錄 誰擋了我的神經網路?(三)