Android内存优化总结

395 阅读7分钟

Java

内存分配

静态存储区(方法区):

主要存放静态数据、全局static数据和常量。这块内存在程序编译时就已经分配好,并且在程序整个运行期间都存在。

栈区 (Stack):

当方法被执行时,方法体内的局部变量(包括基础数据类型、对象的引用)都在栈上创建,并在方法执行结束时这些局部变量所持有的内存将会自动被释放。

堆区 (Heap):

又称动态内存分配,通常就是指直接 new 出来的对象实例或数组存放的内存。这部分内存在不使用时将会由 Java 垃圾回收器来负责回收。
【GC回收的就是Heap中的内存】

public class Sample {
    //s1/mSample1在堆区
    int s1 = 0;
    Sample mSample1 = new Sample();
    public void method() {
        //s2、arr2、mSample2在栈区,对应的引用对象在堆区
        int s2 = 1;
        int[] arr2 = {1,2,3}
        Sample mSample2 = new Sample();
    }
}

Sample mSample3 = new Sample();
mSample3.method();

回收机制

内存泄露是指该内存空间使用完毕之后未回收;
垃圾回收(Garbage Collection,GC)

参考链接
参考链接
参考链接

判断是否为垃圾

引用计数法
  • 为每一个创建的对象分配一个引用计数器,用来存储该对象被引用的个数。当该个数为零,意味着没有人再使用这个对象,可以认为“对象死亡”。
  • 这种方案存在严重的问题,就是无法检测“循环引用”:当两个对象互相引用,即使它俩都不被外界任何东西引用,但它俩的计数都不为零,因此永远不会被回收。
  • 因此,Java 里没有采用这样的方案来判定对象的“存活性”。
可达性分析算法
  • 这种方案是目前主流语言里采用的对象存活性判断方案。
  • 基本思路是把所有引用的对象想象成一棵树,从树的根结点 GC Roots 出发,持续遍历找出所有连接的树枝对象,这些对象则被称为“可达”对象,或称“存活”对象。其余的对象则被视为“死亡”的“不可达”对象,或称“垃圾”;
  • java中可作为GC Root的对象有
    • 虚拟机栈中引用的对象(本地变量表)
    • 方法区中静态属性引用的对象
    • 方法区中常量引用的对象
    • 本地方法栈中引用的对象(Native对象)

回收算法

仅做了解,不细致探究;
垃圾回收机制是算法,在Java之前就有,JVM也未声明具体用哪种算法;

标记-清除算法
标记-整理算法
复制算法
分代收集算法

年轻代、年老代、持久代

引用类型

强引用(StrongReference):

JVM 宁可抛出 OOM ,也不会让 GC 回收;

软引用(SoftReference):

只有在内存空间不足时,才会被回收;

弱引用(WeakReference):

在 GC 时,一旦发现有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存;

虚引用(PhantomReference):

任何时候都可以被GC回收


Android 内存优化

微信 Android 终端内存优化实践

Android 内存管理机制

dev_doc

共享内存
  • Android应用的进程都是从一个叫做Zygote的进程fork出来的。Zygote进程在系统启动并且载入通用的framework的代码与资源之后开始启动。这使得大多数的RAM pages被用来分配给framework的代码,同时使得RAM资源能够在应用的所有进程之间进行共享。
  • 大多数static的数据被mmapped到一个进程中。
  • 大多数情况下,Android通过显式的分配共享内存区域(例如ashmem或者gralloc)来实现动态RAM区域能够在不同进程之间进行共享的机制。
分配与回收内存
限制应用的内存
  • ActivityManager.getMemoryClass()可以用来查询当前应用的Heap Size阈值
应用切换操作
  • Android系统并不会在用户切换应用的时候做交换内存的操作。Android会把那些不包含Foreground组件的应用进程放到LRU Cache中。
  • 当系统开始进入Low Memory的状态时,它会由系统根据LRU的规则与应用的优先级,内存占用情况以及其他因素的影响综合评估之后决定是否被杀掉。

内存优化的几个方面

内存抖动
内存泄漏
内存溢出
线上、线下的检查和解决方案

一、内存抖动

短时间内大量的对象被创建又马上被释放;
虽然不会内存泄漏,但可能触发GC操作,影响帧率;

原因
  • for循环创建大量对象
  • onDraw创建大量对象(经常重新绘制)
  • string大量拼接
解决
  • 复用对象
  • string --> StringBuilder

二、内存泄漏 ML

无用
GC Root可达

常见原因
  • 静态变量:单例、静态属性引用...
  • 内部类:Handler、Thread...
  • 资源对象使用后未关闭: cursor、Bitmap、BraodcastReceiver、ContentObserver...
Android造成内存泄漏的场景
static类/变量
  • 原因
    • 单例的静态特性导致其生命周期和应用一样长
    • 单例模式、静态类...
  • 解决:
    • 如需传入context,尽量使用ApplicationContext
    • 软引用
private static ScrollHelper mInstance;    
private ScrollHelper() {}    
public static ScrollHelper getInstance() {        
    if (mInstance == null) {            
        synchronized (ScrollHelper.class) {                
            if (mInstance == null) {
                mInstance = new ScrollHelper();
            }
        }
    }        
    return mInstance;
}    

private WeakReference<View> mScrolledViewWeakRef = null;
public void setScrolledView(View scrolledView) {
    mScrolledViewWeakRef = new WeakReference<View>(scrolledView);
}
内部类
  • 原因:
    • 内部类持有外部类引用
    • Handler、Runnable、AsyncTask...
  • 解决:
    • Static + WeakReference
    • dostory的时候释放

WeakReference??
如果要访问Activity,虽然是没办法需要传入引用
但是随时GC啊,如果还在用这个handler那弱引用被回收了的呀???
软引用GC是内容够用不回收,但是Android2.3以后版本中,系统会优先将SoftReference的对象提前回收掉, 即使内存够用,其他和Java中是一样的。所以谷歌官方建议用LruCache(least recentlly use 最少最近使用算法)。

private static class MyHandler extends Handler{
    private final WeakReference<Activity> mAct;
    public MyHandler(TestActivity activity){
        this.mAct = new WeakReference<Activity>(activity);
    }
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        TestActivity testAct = mAct.get();
        if (testAct != null) {
            testAct.tvMsg.setText("this is msg");
        }
    }
}

Handler引起的内存泄漏:
msg.target指向handler,如果任务延迟或等待过长,可能会引起内存泄漏。

注册监听器的泄漏
  • 使用ApplicationContext代替ActivityContext;
  • 在Activity执行onDestory时,调用反注册;
资源操作没有及时回收

Cursor,Stream没有close,View没有recyle

集合中对象没清理造成的内存泄漏

我们通常把一些对象的引用加入到了集合中,当我们不需要该对象时,并没有把它的引用从集合中清理掉,这样这个集合就会越来越大。如果这个集合是static的话,那情况就更严重了。

WebView
  • 当我们不要使用WebView对象时,应该调用它的destory()函数来销毁它,并释放其占用的内存,否则其占用的内存长期也不能被回收,从而造成内存泄露。
  • 解决方案: 为webView开启另外一个进程,通过AIDL与主线程进行通信,WebView所在的进程可以根据业务的需要选择合适的时机进行销毁,从而达到内存的完整释放。

三、内存溢出 OOM

Android内存优化之OOM

什么情况下会发生OOM?
  • 不同的版本、设备表现不同,有事Heap值明显高于ActivityManager.getMemoryClass()也不会发生OOM
如何避免OOM?

减小对象的内存占用
内存对象的重复利用
避免对象的内存泄露
内存使用策略优化

减小对象的内存占用
  • 使用更加轻量的数据结构
    • ArrayMap/SparseArray 代替 HashMap
    • StringBuilder 代替 String 进行字符串操作
  • 避免在Android里面使用Enum
  • 减小Bitmap对象的内存占用
    • inSampleSize:缩放比例
    • decode format:解码格式
  • 使用更小的图片
    • 不同的资源包
    • SVG
内存对象的重复利用
  • 复用系统自带的资源
    • 系统自带color、drawable
  • RecyclerView优化
  • Bitmap对象的复用
    • inBitmap复用
  • onDraw、listeners、StringBuilder
避免对象的内存泄露
  • Activity、listener、register、handler...
  • webview泄漏
  • cursor、bitmap未及时关闭、回收
内存使用策略优化
  • 慎用 large heap
  • 设计合适的缓存大小
  • 资源文件需要选择合适的文件夹进行存放
  • Try catch某些大内存分配的操作
  • 谨慎使用static对象
  • 珍惜Services资源
  • 优化布局层次,减少内存消耗
  • 软引用:
    • Java上按照强、软、弱、虚引用的顺序回收
    • Android上回首先回收软引用,其它顺序相同