介绍

Android系统中,内存分配与释放分配在一定程度上会影响App性能的—鉴于其使用的是类似于Java的GC回收机制,因此系统会以消耗一定的效率为代价,进行垃圾回收。
在中国有句老话:”由俭入奢易,由奢返俭难”。而此谚语也似乎正适应于Android的内存使用。GC回收机制给程序员省去了像C语言程序员那样手动释放内存的工作,但是也带来了一系列的”雷”—动辄内存泄漏,再甚者稍微不慎就会OOM。
这篇文章将会介绍Android的内存管理机制并解释几种在此机制下对内存有影响的几个比较关键的因素。另外,还会介绍如何提高内存管理、检测并避免内存泄漏,以及如何分析内存分配情况

Here we go!!

Android内存管理机制

Android内存模型并没有交换空间(swap
space)的概念,而是使用分页(paging)和内存映射(memory-mapping)管理内存,这意味着不管是分配新的对象还是使用已有的映射页这些内存仍然被占据在RAM里而不能被扇出。因此完全释放你app内存的唯一方式是释放对象引用以便于能被垃圾回收器回收。

Dalvik虚拟机为每一个App分配相应大小的可用内存空间,从2M开始到32M(此最大值根据不同的厂商一般会有不同),不可否认,在当前国内各大手机厂商疯狂的拼硬件的时代,这个每个App的可用内存甚至被提高到了256M,这有效的避免了很多OOM的情况,但是如果程序员因此就不管内存管理任意而为,会为此付出严重代价的(App高卸载率).

Android系统会将在后台运行的App进程保存在一个LRU
cache中(不懂的自行百度)。当系统内存紧张时,它会根据LRU的策略kill掉一些优先级比较低的进程。当然,究竟哪一个App是当前占用内存最大的程序也是它kill进程时所考虑的一个因素。如果你希望自己的App在后台运行时能尽可能长的”活着”,不被系统kill掉,就要好好的思考如何避免被kill。比如在App转到后台运行之前,尽可能的将没有用的内存给释放掉,这样会减少Android系统打印错误日志甚至终止App的可能性。

如何提高Android内存使用

Android系统是世界上使用率最高的手机系统。每年都有成千上万的年轻人转入到开发Android系统的行列中,但是这些人中,能真正写出稳定、可扩展性强的代码的还是少数。

以下是提高内存使用的几条建议:
  1. 慎用桥接模式,虽然从程序的设计角度来看,抽象能够帮助我们创建更加灵活的软件架构。但是在手机系统中,这种设计模式有可能会造成很多副作用。除非大有必要,否则尽量不要用桥接模式
  2. 避免使用枚举Enum,一个Enum分配的空间是一个普通常量的两倍,因此尽量少使用枚举
  3. 试着使用Android框架优化后的数据容器,譬如:SparseArray, SparseBooleanArray, 以及
    LongSparseArray containers. 使用这些类来替代HashMap的使用。原因是传统的 HashMap
    在内存上的实现十分的低效,因为它需要为 HashMap 中每一项在内存中建立映射关系. 另外,
    SparseArray类非常高效因为它避免了对key和value的自动封箱. 万事都有两面性,这些个被优化过的容器也不例外,千万记住SparseArray等容器并不适应于内部元素很多的集合,当集合的长度超过1000条时,使用SparseArray进行增删改查的效率远比HashMap低
  4. 避免创建不需要的对象。对于生命周期较短的临时变量,尽量想办法规避掉每次都要去创建它,这样GC回收被强制调用机会就会更少,留给Android系统进行UI渲染或者音频加载的时间就会更多,从而避免了卡顿现象
  5. 检测App内存中的可用堆的大小,在代码中可以通过动态的调用ActivityManager::getMemoryClass()方法来查询你的App中的可用内存堆大小。如果系统检测到需要分配的内存大小超过了此值,则会抛出OOM错误
  6. **可以适当适应onTrimMemory回调方法。OnTrimMemory 回调是 Android 4.0 之后提供的一个API,这个
    API 是提供给开发者的,它的主要作用是提示开发者在系统内存不足的时候,通过处理部分资源来释放内存,从而避免被 Android
    系统杀死。这样应用在下一次启动的时候,速度就会比较快。—详情请参阅Android内存优化—OnTrimMemory
  7. 当使用Service应当小心小心再小心!当你需要启动一个服务在后台执行一项任务时,应当在其完成工作之后尽快的停止此服务。可以考虑使用IntentService—当在子线程完成耗时操作之后,IntentService会自动停止并结束自身。然而在实际开发中经常会碰到需要服务去执行一项耗时比较长的任务,比如:音乐播放器,下载APP等等。像这样的应用可以分隔为两个进程:一个进程负责
    UI 工作, 另外一个则在后台服务中运行其它的工作. 在AndroidManifest 文件中为各个组件申明 android:process
    属性就可以分隔为不同的进程。注意一点:在后台运行的Service绝对不能处理或者持有任何UI,否则系统可能会分配双倍甚至三倍的空间来维护UI资源!!
  8. 当你加载 bitmap 时,
    需要根据当前设备的分辨率加载相应分辨率的bitmap进入内存,如果下载下来的原图分辨率比设备分辨率高则要压缩它.
    要小心bitmap的分辨率增加后所占用的内存也要进行相应的增加(平方级increase2的增长), 因为它是根据x和y的大小来增加内存占用的
  9. 使用代码混淆工具 ProGuard 通过去除没有用的代码和通过语义模糊来重命名类, 字段和方法来缩小,
    优化和混淆你的代码. 使用它能使你的代码更简洁, 更少量的RAM映射页.如果构建apk后你没有做后续的任何处理(包括根据你的证书进行签名),
    你必须运行 zipalign 工具为你的apk进行优化,
    如果不这样做会导致你的应用使用更多的内存,zipalign之后像资源这样的东西不会再从apk中映射(mmap)入内存.注意:goole play
    store 不接受没有进行zipalign的apk

针对以上几条,后续会单独再post几篇blog单独讲解。

如何避免内存泄漏

程序员在分配内存时如果考虑到了上述9条建议,或许会给App在效率上带来不小的收益,并且可以在后台时依然坚挺(更持久!)。
但是这一切的努力都会因为一个叫做内存泄漏的东东而萎了! 这玩意就如同可乐的存在一样,少喝一点还能扛得住,但是多了的话。。你懂得!
以下是几个常见的造成内存泄漏的情况:

  • 当查询完数据库之后,及时关闭Cursor对象。
  • 记得在Activity的onPause方法中调用unregisterReceiver()方法,解注册广播
  • 避免Content内存泄漏,比如在4.0.1之前的版本上不要讲Drawer对象置为static。当一个Drawable绑定到了View上,实际上这个View对象就会成为这个Drawable的一个callback成员变量,上面的例子中静态的sBackground持有TextView对象lable的引用,而lable只有Activity的引用,而Activity会持有其他更多对象的引用。sBackground生命周期要长于Activity。当屏幕旋转时,Activity无法被销毁,这样就产生了内存泄露问题。
  • 尽量不要在Activity中使用非静态内部类,因为非静态内部类会隐式持有外部类实例的引用,当非静态内部类的引用的声明周期长于Activity的声明周期时,会导致Activity无法被GC正常回收掉。
  • 谨慎使用线程Thread!!这条是很多人会犯的错误: Java中的Thread有一个特点就是她们都是直接被GC
    Root所引用,也就是说Dalvik虚拟机对所有被激活状态的线程都是持有强引用,导致GC永远都无法回收掉这些线程对象,除非线程被手动停止并置为null或者用户直接kill进程操作。所以当使用线程时,一定要考虑在Activity退出时,及时将线程也停止并释放掉
  • 使用Handler时,要么是放在单独的类文件中,要么就是使用静态内部类。因为静态的内部类不会持有外部类的引用,所以不会导致外部类实例的内存泄露–详情请参阅Android中Handler引起的内存泄露

如何分析内存的使用情况

在Mac终端(windows的cmd)中,可以使用adb
logcat命令来查看或者统计内存的具体使用情况,另外还可以指定包名来查看相应App的内存使用情况。除此之外,还可以使用三方的工具来分析Android内存的使用情况,比如:DDMS、MAT(Memory
Analyzer tool).

在adb logcat中,通常能看到GC相关的log如下图所示

GC_Reason 触发GC回收的原因,可能包含以下几种情况:

  • GC_FOR_ALLOC, 这个是说我们的应用尝试去分配内存而这时候和heap已经快满了(不够用了),这个时候系统会把我们的应用停下来然后进行内存回收,通常heap size会增大
  • GC_CONCURRENT,这个应该的当我们的Heap size 快要被填满的时候触发的一个并发的内存回收
  • GC_EXPLICIT,这个是主动调用系统gc方法触发的GC(在DDMS 点击GC就可以看到)
  • GC_HPROF_DUMP_HEAP 我们在做内存分析创建HPROF(MAT可以分析该文件)的时候会打印

Amount feed 表示本次垃圾收集释放了多少内存

Heap_stats 当前空闲内存占总内存的百分比

External memory stats 表示API 10及以下的外部分配内存,已分配内存/导致垃圾回收的阈值

Pause_time 应用暂停的时间

通常情况下,生成的GC log越大,表示内存的分配与释放发生的频率越高,这种情况下往往会非常影响用户体验!

使用DDMS查看并追踪堆内存的分配情况

通过DDMS,程序员可以很轻松的检测指定进程的内存分配情况。你可以通过“Heap”标签查看最新的实时的堆内存信息,这样可以帮助你辨别出究竟是哪一个操作最有可能造成大量的内存分配。
“Allocation Tracker”
标签显示的是最近所有的内存分配—包含分配对象的类型,是在哪个线程中分配等信息。一下图片演示的是使用DDMS展示进程信息—包含了当前进程、对内存分配统计信息。

关于Android内存优化你应该知道的一切的更多相关文章

  1. 大礼包!ANDROID内存优化(大汇总)

    写在最前: 本文的思路主要借鉴了2014年AnDevCon开发者大会的一个演讲PPT,加上把网上搜集的各种内存零散知识点进行汇总.挑选.简化后整理而成. 所以我将本文定义为一个工具类的文章,如果你在A ...

  2. ANDROID内存优化——大汇总(转)

    原文作者博客:转载请注明本文出自大苞米的博客(http://blog.csdn.net/a396901990),谢谢支持! ANDROID内存优化(大汇总——上) 写在最前: 本文的思路主要借鉴了20 ...

  3. Android内存优化之——static使用篇(使用MAT工具进行分析)

    这篇文章主要配套与Android内存优化之——static使用篇向大家介绍MAT工具的使用,我们分析的内存泄漏程序是上一篇文章中static的使用内存泄漏的比较不容易发现泄漏的第二情况和第三种情况—— ...

  4. ANDROID内存优化(大汇总——中)

    转载请注明本文出自大苞米的博客(http://blog.csdn.net/a396901990),谢谢支持! 写在最前: 本文的思路主要借鉴了2014年AnDevCon开发者大会的一个演讲PPT,加上 ...

  5. 【腾讯Bugly干货分享】Android内存优化总结&实践

    本文来自于腾讯Bugly公众号(weixinBugly),未经作者同意,请勿转载,原文地址:https://mp.weixin.qq.com/s/2MsEAR9pQfMr1Sfs7cPdWQ 导语 智 ...

  6. Android内存优化(三)详解内存分析工具MAT

    前言 在这个系列的前四篇文章中,我分别介绍了DVM.ART.内存泄漏和内存检测工具的相关知识点,这一篇我们通过一个小例子,来学习如何使用内存分析工具MAT. 1.概述 在进行内存分析时,我们可以使用M ...

  7. Android内存优化1 了解java内存分配 1

    开篇废话 今天我们一起来学习JVM的内存分配,主要目的是为我们Android内存优化打下基础. 一直在想以什么样的方式来呈现这个知识点才能让我们易于理解,最终决定使用方法为:图解+源代码分析. 欢迎访 ...

  8. Android内存优化(二)DVM和ART的GC日志分析

    相关文章 Android内存优化系列 Java虚拟机系列 前言 在Java虚拟机(三)垃圾标记算法与Java对象的生命周期这篇文章中,提到了Java虚拟机的GC日志.DVM和ART的GC日志与Java ...

  9. Android内存优化(一)DVM和ART原理初探

    相关文章 Android内存优化系列 Java虚拟机系列 前言 要学习Android的内存优化,首先要了解Java虚拟机,此前我用了多篇文章来介绍Java虚拟机的知识,就是为了这个系列做铺垫.在And ...

  10. Android内存优化大全(中)

    转载请注明本文出自大苞米的博客(http://blog.csdn.net/a396901990),谢谢支持! 写在最前: 本文的思路主要借鉴了2014年AnDevCon开发者大会的一个演讲PPT,加上 ...

随机推荐

  1. Postgres 将查询结果同时插入数据表

    INSERT INTO table [ ( column [, ...] ) ] { DEFAULT VALUES | VALUES ( { expression | DEFAULT } [, ... ...

  2. mysql安装 以及跳过密码登录重设

    修改MySQL的登录设置: vi /etc/my.cnf 在[mysqld]的段中加上一句:skip-grant-tables 例如: [mysqld] datadir=/var/lib/mysql ...

  3. Linux 必要软件的安装与配置

    主要是记录一下,免得下次重装系统后又到处搜索.. 一.必要软件的安装 JDK 下载 tar.gz:http://www.oracle.com/technetwork/java/javase/downl ...

  4. linux文件备份到windows方法

    目录 背景 方案 过程记录 在windows上创建共享目录 将windows上共享的目录绑定到/mnt目录下 问题处理 背景 需编写部门wiki备份数据脚本.但wiki部署在linux上,而需将备份数 ...

  5. [NOI2003][bzoj1507] 文本编辑器 editor [splay]

    其实看明白了就是一道水题 毕竟模板 splay敲一发,插入一个串的时候先把它构建成一棵平衡树,再挂到原来的splay上面去即可 没别的了,上代码 #include<iostream> #i ...

  6. js复制到粘贴板

    http://www.cnblogs.com/52fhy/p/5383813.html(移动端有兼容性问题) 要页面加载完直接绑定事件,否则第一次点击是绑定事件,第二次才触发事件 移动端需要设置tex ...

  7. [暑假集训--数论]poj2034 Anti-prime Sequences

    Given a sequence of consecutive integers n,n+1,n+2,...,m, an anti-prime sequence is a rearrangement ...

  8. Python之时间:time模块

    import time   对于时间,使用最频繁的模块 1.获取当前时间 (1)时间戳 time.time() 时间戳:从1970年1月1日0点开始到现在按秒计算的偏移量 (2)时间元组 time.l ...

  9. HTML+CSS 滚动条样式自定义 - 适用于 div,iframe, html 等

    友言:这两天被滚动条整的无与伦比,在此做下总结: 首先自定义浏览器滚动条的实现原理:计算浏览器滚动条的高度,层级1的高度与滚动条的总高度是一样的,通过相似比例计算: 浏览器滚动条总高度 :滚动条高度 ...

  10. mac 应用程序安装目录

    java 安装目录 :/Library/Java/JavaVirtualMachines/jdk1.8.0_<more numbers>.jdk/Contents/Home maven 安 ...