Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android開發之內存管理

Android開發之內存管理

編輯:關於Android編程

概念

應用的開發離不開存儲,存儲分為網絡、內存、SDCard文件存儲以及外部SDCard2文件存儲,開發中一定要注意好內存管理以免oom、卡頓等不好的用戶體驗,同時還要注意變量的回收,避免內存洩漏。下面呢先來了解一些基本的相關專業術語。

RAM(random access memory)隨機存取存儲器即內存

寄存器(Registers):速度最快的存儲場所,因為寄存器位於處理器內部,我們在程序中無法控制

棧(Stack):存放基本類型的數據和對象的引用,但對象本身不存放在棧中,而是存放在堆中

堆(Heap):堆內存用來存放由new創建的對象和數組。在堆中分配的內存,由Java虛擬機的自動垃圾回收器(GC)來管理。

靜態域(static field): 靜態存儲區域就是指在固定的位置存放應用程序運行時一直存在的數據,Java在內存中專門劃分了一個靜態存儲區域來管理一些特殊的數據變量如靜態的數據變量

常量池(constant pool):虛擬機必須為每個被裝載的類型維護一個常量池。常量池就是該類型所用到常量的一個有序集和,包括直接常量(string,integer和floating point常量)和對其他類型,字段和方法的符號引用。

非RAM存儲:硬盤等永久存儲空間

堆棧的特點對比

棧:當定義一個變量時,Java就在棧中為這個變量分配內存空間,當該變量退出該作用域後,Java會自動釋放掉為該變量所分配的內存空間,該內存空間可以立即被另作他用。

堆:當堆中的new產生數組和對象超出其作用域後,它們不會被釋放,只有在沒有引用變量指向它們的時候才變成垃圾,不能再被使用。即使這樣,所占內存也不會立即釋放,而是等待被垃圾回收器收走。這也是Java比較占內存的原因。

棧:存取速度比堆要快,僅次於寄存器。但缺點是,存在棧中的數據大小與生存期必須是確定的,缺乏靈活性。

堆:堆是一個運行時數據區,可以動態地分配內存大小,因此存取速度較慢。也正因為這個特點,堆的生存期不必事先告訴編譯器,而且Java的垃圾收集器會自動收走這些不再使用的數據。

棧:棧中的數據可以共享,它是由編譯器完成的,有利於節省空間。

如果你對堆棧還不夠清晰明了,那麼請看下面一圖

\

內存洩漏也稱作“存儲滲漏”,用動態存儲分配函數動態開辟的空間,在使用完畢後未釋放(堆棧開辟的存儲空間存儲的值),結果導致一直占據該內存單元。直到程序結束。(其實說白了就是該內存空間使用完畢之後未回收)即所謂內存洩漏<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxoNCBpZD0="內存分析">內存分析

我們要怎麼知道內存發生洩漏呢,需要借助內存分析工具MAT,或者使用開源項目LeakCanary,首先呢我們先到官網找到關於MAT的使用介紹,了解一個概要再來實踐吧。

首先我們需要安裝MAT,如果你是Eclipse開發還未轉Android Studio,那麼只需要安裝MA插件即可,看下圖(最新地址: http://download.eclipse.org/mat/1.5/update-site/)。

\

當然你如你用Android Studio ,但是不想用Eclipse來分析,那麼可以下載獨立的MAT,解壓後得到下圖效果,雙擊運行打開即可。

① Eclipse開發工具內建立一個測試的java程序,測試代碼如下

import java.util.ArrayList;
import java.util.List;

public class Main {


/**
   * @param args
   */

  public static void main(String[] args) {
    List list = new ArrayList();
    while (1<2){
      list.add("OutOfMemoryError soon");
    }

  }

} 

運行就會爆內存洩漏問題

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Unknown Source)
    at java.util.Arrays.copyOf(Unknown Source)
    at java.util.ArrayList.grow(Unknown Source)
    at java.util.ArrayList.ensureExplicitCapacity(Unknown Source)
    at java.util.ArrayList.ensureCapacityInternal(Unknown Source)
    at java.util.ArrayList.add(Unknown Source)
    at test.Main.main(Main.java:16)

② 生成xx.hprof文件,利用MAT進行分析,這裡生成 xx.hprof文件有兩種方案,eclipse 下的 java工程項目已run as >run config配置為例(另一種稍後提到)

\

添加VM arguments和配置輸出xx.hprof文件的目錄,運行爆上述錯誤,打開目錄即可看到xx.hprof文件

當你滿心歡喜的以為即將要成功的時候,突然給你整這麼一處,這是腫麼回事呢?

原因是: android的虛擬機導出的內存文件hprof文件格式與標准的 java hprof文件格式標准不一樣,根本原因兩者的虛擬機不一致導致的。只需要使用SDK中自帶的轉換工具轉換就可以了,hprof-conv 源文件 目標文件在尋找解決這個問題的途中,遇到一個大b坑,http://blog.csdn.net/pugongying1988/article/details/9122699該篇博客告訴我在tools工具目錄下,我命令行怎麼都不行說找不到程序,我懷疑是不是我沒安裝插件,重裝插件後還是不行,幾經折騰在這裡找到它

\

亮瞎了這是tools目錄麼,頓時萬馬奔騰,直呼尼瑪!!回歸正題,切換到該目錄下調用hprof-conv 命令重新輸出xx.hprof文件(為了方便,把新舊的xx.hprof文件都放在了改目錄下)

說好的不是版本問題(不是絕對的),經過幾次驗證,特麼就是eclipse導出版本問題(run as config配置導出的版本問題,具體原因沒深究),最後通過DDMS導出的xx.hprof文件就沒問題了(補充說明:原博客確定了是不是版本問題這樣做是正確的,我們使用android studio開發,通過DDMS導出那個真不是版本問題,通過上面命令就可以了)

Histogram
列出了集合的對象實例,每種類型的實例集合的 shallow size 和 retained size . shallow size指的是對象所消耗的內存大小,如每個對象引起消耗4個字節,或者8個字節,取決於你的操作系統(32位,還是64位), retained size的概念依賴於Retained set 的概念,Retained set 指的是當對象X被回收時,所有被垃圾回收器移除的對象集合, Retained size 即是Retained set所保持的內存大小。

具體操作如下,Overrview視圖下面進入

按照規則過濾後列表,接著跟蹤

再通過Path to GC Root(被JVM持有的對象,如當前運行的線程對象,被systemclass loader加載的對象被稱為GC Roots, 從一個對象到GC Roots的引用鏈被稱為Path to GC Roots, 通過分析Path to GC Roots可以找出JAVA的內存洩露問題,當程序不在訪問該對象時仍存在到該對象的引用路徑。 )跟蹤變量沒被回收的具體位置

根據提供Demo的xx.hprof文件跟蹤發現HomeActivity裡面調用了DrawableHelper類,內部mContext變量引起內存洩漏,這裡的mContext變量如下(由於內部其他多個方法設置屬性需要Context對象,本應該傳入一次放到構造方法裡面,private 不要static屬性就好,但是呢不同的Activity調用就會造成Context對象問題,所以最好傳入的Context對象為activity.getApplicationContext,亦或者其他今天屬性方法傳入參數,這樣DrawableHeper不用保存Context實例引用,由此分析讓我明白了一點:不是什麼時候都適合用單例模式,要具體問題具體分析)


public class DrawableHelper {

    protected static DrawableHelper mDrawableHelper;
    protected static Context mContext;

    private DrawableHelper() {

    }

    public static DrawableHelper getInstance(Context context) {
        if (mDrawableHelper == null) {
            synchronized (DrawableHelper.class) {
                if (mDrawableHelper == null) {
                    mDrawableHelper = new DrawableHelper();
                }
            }
        }
        mContext = context;
        return mDrawableHelper;
    }

內存優化要素

① 重復字符串是內存浪費的一個典型例子:多個字符數組具有相同的內容。字符數組的內容通常會給出如何減少重復的思想。

② 空集合空間不存儲任何數據。如果只有少數集合保存數據,考慮延遲初始化,即只在需要時創建集合。

③集合通常是創建一個默認初始容量。許多低填充率的集合表明,初始容量可以減少。

④ 軟引用靜態資源

⑤ 使用 Andorid 框架中優化過的數據容器,例如 SparseArray,SparseBooleanArray 和 LongSparseArray。類似於 HashMap 這一類的容器的效率不是很高,因為在每個 Map 中對於每一次的存放數據,他都需要獨立一個單獨的 Entry 對象進行傳芳。而 SparseArray 由於禁止系統自動封裝鍵值對,因此他更加有效率。並且你不需要擔心丟失掉原有信息(AbsListView子類控件紀錄checked屬性和position)

⑥避免依賴注入框架,使用類似於 Xutils的ViewUtils注解模塊的依賴注射框架,或許會使你的代碼變得更加漂亮,因為他們能夠減少你需要寫的代碼,並且為測試或者在其他條件改變的情況下,提供一種自適應的環境。但是,這些框架在初始化的時候會因為注釋而消耗大量的工作在掃描你的代碼上,這會讓你的代碼在進行內存映射的時候花費更多的資源。雖然這些內存能夠被 Android 進行回收,但是等待整個分頁被釋放需要很長一段時間。

⑦使用混淆器ProGuard移除不必要的代碼。

⑧ 不要因為某個需求而使用大體積類庫,比如圓形頭像只需要一個圓形頭像即可沒必要使用開源的PhotoView庫,相對輕量級的CircleImageView跟適合或者自己自定義裁剪控件。

⑨ 常量用static final修飾,(context不要用static修飾,容易造成內存洩漏)

⑩ 不用的變量,即使釋放NULL

第三方庫LeakCanary實踐

地址:https://github.com/square/leakcanary
項目依賴

 dependencies {
   debugCompile 'com.squareup.leakcanary:leakcanary-android:1.4-beta2'
   releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta2'
   testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta2'
 }

Application初始化庫

LeakCanary.install(this);

運行後如果程序出現內存洩漏,會有提示,找到Leaks圖標打開可以查看洩露位置,分析原因,同時我們還可以通share heap dump,分享.hprof文件到電腦,通過MAT進行詳盡的定位分析。(.hprof文件比較大建議wifi下進行傳輸)

\

小結

東拼西湊還是完成了這篇blog,LeakCanary集成到項目幫助我們分析洩露位置,如果還不能清晰的分析出具體原因,可以到處.hprof文件通過MAT分析,最後針對個人做個簡短小結:

①以後開發一定要注意單例模式的運用了,太多的instance,並不是所有的類都需要。

② static 修飾常量都改為static final(個別不能final嘗試去掉static修飾)

③Context 、Activity都不要用static修飾

④ 內存洩漏:開辟的堆棧的存儲引用的管理,干掉非存活狀態的堆棧引用,及時釋放。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved