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的垃圾收集器会自动收走这些不再使用的数据。

栈:栈中的数据可以共享,它是由编译器完成的,有利于节省空间。

如果你对堆栈还不够清晰明了,那么请看下面一图

内存泄漏也称作“存储渗漏”,用动态存储分配函数动态开辟的空间,在使用完毕后未释放(堆栈开辟的存储空间存储的值),结果导致一直占据该内存单元。直到程序结束。(其实说白了就是该内存空间使用完毕之后未回收)即所谓内存泄漏

内存分析

我们要怎么知道内存发生泄漏呢,需要借助内存分析工具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<String> list = new ArrayList<String>();
    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修饰

④ 内存泄漏:开辟的堆栈的存储引用的管理,干掉非存活状态的堆栈引用,及时释放。


参考资料

http://blog.csdn.net/a396901990/article/details/37914465

http://www.jianshu.com/p/c49f778e7acf

http://blog.csdn.net/pugongying1988/article/details/9122699

http://wiki.eclipse.org/MemoryAnalyzer#HPROF_dumps_from_Sun_Virtual_Machines

http://blog.csdn.net/xu_fu/article/details/45678373

http://blog.csdn.net/tiantangrenjian/article/details/39182293

推荐博客

http://blog.csdn.net/a396901990/article/details/38904543

http://blog.csdn.net/a396901990/article/details/38707007

时间: 2024-11-05 23:23:46

Android开发之内存管理的相关文章

Android中的内存管理机制以及正确的使用方式

概述 从操作系统的角度来说,内存就是一块数据存储区域,属于可被操作系统调度的资源.现代多任务(进程)的操作系统中,内存管理尤为重要,操作系统需要为每一个进程合理的分配内存资源,所以可以从两方面来理解操作系统的内存管理机制. 第一:分配机制.为每一个进程分配一个合理的内存大小,保证每一个进程能够正常的运行,不至于内存不够使用或者每个进程占用太多的内存. 第二:回收机制.在系统内存不足打的时候,需要有一个合理的回收再分配的机制,以保证新的进程可以正常运行.回收的时候就要杀死那些正在占有内存的进程,操

[转载]对iOS开发中内存管理的一点总结与理解

对iOS开发中内存管理的一点总结与理解 做iOS开发也已经有两年的时间,觉得有必要沉下心去整理一些东西了,特别是一些基础的东西,虽然现在有ARC这种东西,但是我一直也没有去用过,个人觉得对内存操作的理解是衡量一个程序员成熟与否的一个标准.好了,闲话不说,下面进入正题. 众所周知,ObjectiveC的内存管理引用的一种叫做“引用计数“ (Reference Count)的操作方式,简单的理解就是系统为每一个创建出来的对象,(这里要注意,只是对象,NSObject的子类,基本类型没有‘引用计数’)

【转】Android中的内存管理--不错不错,避免使用枚举类型

原文网址:http://android-performance.com/android/2014/02/17/android-manage-memory.html 本文内容翻译自:http://developer.android.com/training/articles/memory.html 随机存取存储器(RAM)再任何软件开发环境中都是宝贵的资源,但是在移动操作系统中,内存资源更为宝贵,使用时也会收到限制.虽然Android的Dalvik虚拟机有运行时的垃圾回收机制,但是这不意味着你的A

ARM裸机开发中内存管理库RT_HEAP的使用

在使用arm芯片进行裸机开发的时候,很多时候都需要内存管理的功能,我们可以使用自己写的内存管理程序,也可以直接使用标准库,不过我一般比较喜欢标准库,速度快,今天就来说说在C语言环境下怎么样进行内存的动态使用 首先,应该初始化C堆,初始化代码如下 #include "malloc.h" #pragma import (__use_realtime_heap) //这个函数在rt_heap.h中声明,需要用户自己去实现,返回任意值 unsigned __rt_heap_extend(uns

iOS开发ARC内存管理技术要点

本文来源于我个人的ARC学习笔记,旨在通过简明扼要的方式总结出iOS开发中ARC(Automatic Reference Counting,自动引用计数)内存管理技术的要点,所以不会涉及全部细节.这篇文章不是一篇标准的ARC使用教程,并假定读者已经对ARC有了一定了解和使用经验.详细的关于ARC的信息请参见苹果的官方文档与网上的其他教程:) 本文的主要内容: ARC的本质 ARC的开启与关闭 ARC的修饰符 ARC与Block ARC与Toll-Free Bridging 技术交流新QQ群:41

iOS开发_内存管理

1.ObjC中对象是存储在堆中的,系统并不会自动释放堆中的内存,但是基本类型是由系统自己管理的,放在栈上. 2.要开发一个程序并不难,但是优秀的程序则更测重于内存管理,它们往往占用内存更少,运行更加流畅. 3.野指针就是指针所指向的内存地址已经被系统回收,而指针没有被置为空;内存泄露就是该被回收的对象没有被回收,导致内存被占用,也就是内存泄露. 4.在ObjC中给空对象发送消息是不会引起错误的. 5.属性参数: retain:先release,再retain,用于非字符串对象 copy:先rel

(转)iOS开发ARC内存管理技术要点

转自:http://www.cnblogs.com/flyFreeZn/p/4264220.html 本文来源于我个人的ARC学习笔记,旨在通过简明扼要的方式总结出iOS开发中ARC(Automatic Reference Counting,自动引用计数)内存管理技术的要点,所以不会涉及全部细节.这篇文章不是一篇标准的ARC使用教程,并假定读者已经对ARC有了一定了解和使用经验.详细的关于ARC的信息请参见苹果的官方文档与网上的其他教程:) 本文的主要内容: ARC的本质 ARC的开启与关闭 A

Android 开发 Eclipse 内存调整

在使用Eclipse的过程中,有时会遇到使用Java虚拟机内存不够的情况,这时Eclipse就会提示你重启,我们可以通过设定Eclipse启动参数来调节使用Java虚拟机内存. 右键点击Eclipse的快捷方式,选择属性,在目标的Text里加上 -vmargs -Xmx192M,设定初始化使用Java虚拟机最大内存为192M,也可以设定为其他值. -Xmx 这个参数是设定使用的最大内存 -Xms 这个参数是设定使用的最小内存 两个参数可以同时使用,也可单独使用. 用编译时报内存不够,翻了一下资料

iOS开发--漫谈内存管理(一)

1.MRC与ARC 苹果提供两种内存管理机制:一种是MRC(manual reference count),即手动引用计算:另一种是ARC(auto reference count),即自动引用计数.手动引用计,顾名思义,需要程序员主动调用retain.release等方法来管理内存,而自动引用计数方式,则是编译器在编译阶段把内存管理代码自动插入在需要手动调用的地方.这样就把程序员从复杂的管理内存的工作中解放出来,将更多精力放在实现具体的业务逻辑上.需要注意的是,ARC要在xcode4.2或者以