Android中的内存管理机制

分配机制

Android为每个进程分配内存的时候,采用了弹性的分配方式,也就是刚开始并不会一下分配很多内存给每个进程,而是给每一个进程分配一个“够用”的量。这个量是根据每一个设备实际的物理内存大小来决定的。随着应用的运行,可能会发现当前的内存可能不够使用了,这时候Android又会为每个进程分配一些额外的内存大小。但是这些额外的大小并不是随意的,也是有限度的,系统不可能为每一个App分配无限大小的内存。

Android系统的宗旨是最大限度的让更多的进程存活在内存中,因为这样的话,下一次用户再启动应用,不需要重新创建进程,只需要恢复已有的进程就可以了,减少了应用的启动时间,提高了用户体验。

回收机制

Android对内存的使用方式是“尽最大限度的使用”,这一点继承了Linux的优点。Android会在内存中保存尽可能多的数据,即使有些进程不再使用了,但是它的数据还被存储在内存中,所以Android现在不推荐显式的“退出”应用。因为这样,当用户下次再启动应用的时候,只需要恢复当前进程就可以了,不需要重新创建进程,这样就可以减少应用的启动时间。只有当Android系统发现内存不够使用,需要回收内存的时候,Android系统就会需要杀死其他进程,来回收足够的内存。但是Android也不是随便杀死一个进程,比如说一个正在与用户交互的进程,这种后果是可怕的。所以Android会有限清理那些已经不再使用的进程,以保证最小的副作用。

Android杀死进程有两个参考条件:
进程优先级:

Android为每一个进程分配了优先级的概念,优先级越低的进程,被杀死的概率就更大。Android中总共有5个进程优先级。具体含义这里不再给出。
- 前台进程:正常不会被杀死
- 可见进程:正常不会被杀死
- 服务进程:正常不会被杀死
- 后台进程:存放于一个LRU缓存列表中,先杀死处于列表尾部的进程
- 空进程:正常情况下,为了平衡系统整体性能,Android不保存这些进程

回收收益:

当Android系统开始杀死LRU缓存中的进程时,系统会判断每个进程杀死后带来的回收收益。因为Android总是倾向于杀死一个能回收更多内存的进程,从而可以杀死更少的进程,来获取更多的内存。杀死的进程越少,对用户体验的影响就越小。

官方推荐的App内存使用方式
  • 当Service完成任务后,尽量停止它。因为有Service组件的进程,优先级最低也是服务进程,这会影响到系统的内存回收。IntentService可以很好地完成这个任务。
  • 在UI不可见的时候,释放掉一些只有UI使用的资源。系统会根据onTrimMemory()回调方法d的TRIM_MEMORY_UI_HIDDEN等级的事件,来通知App UI已经隐藏了。
  • 在系统内存紧张的时候,尽可能多的释放掉一些非重要资源。系统会根据onTrimMemory()回调方法来通知内存紧张的状态,App应该根据不同的内存紧张等级,来合理的释放资源,以保证系统能够回收更多内存。当系统回收到足够多的内存时,就不用杀死进程了。
  • 检查自己最大可用的内存大小。这对一些缓存框架很有用,因为正常情况下,缓存框架的缓存池大小应当指定为最大内存的百分比,这样才能更好地适配更多的设备。通过getMemoryClass()和getLargeMemoryClass()来获取可用内存大小的信息。
  • 避免滥用Bitmap导致的内存浪费。
    根据当前设备的分辨率来压缩Bitmap是一个不错的选择,在使用完Bitmap后,记得要使用recycle()来释放掉Bitmap。使用软引用或者弱引用来引用一个Bitmap,使用LRU缓存来对Bitmap进行缓存。
  • 使用针对内存优化过的数据容器。针对移动设备内存有限的问题,Android提供了一套针对内存优化过的数据容器,来替代JDK原生提供的数据容器。但是缺点就是,时间复杂度被提高了。比如SparseArray、SparseBooleanArray、LongSparseArray、
  • 意识到内存的过度消耗。Enum类型占用的内存是常量的两倍多,所以避免使用enum,直接使用常量。
    每一个Java的类(包括匿名内部类)都需要500Byte的代码。每一个类的实例都有12-16 Byte的额外内存消耗。注意类似于HashMap这种,内部还需要生成Class的数据容器,这会消耗更多内存。
  • 抽象代码也会带来更多的内存消耗。如果你的“抽象”设计实际上并没有带来多大好处,那么就不要使用它。
  • 使用nano protobufs 来序列化数据。Google设计的一个语言和平台中立打的序列化协议,比XML更快、更小、更简单。
  • 避免使用依赖注入的框架。依赖注入的框架需要开启额外的服务,来扫描App中代码的Annotation,所以需要额外的系统资源。
  • 使用ZIP对齐的APK。对APK做Zip对齐,会压缩其内部的资源,运行时会占用更少的内存。
  • 合理使用多进程。

Android内存泄漏分析及优化

内存泄漏的根本原因

如上图所示,GC会选择一些它了解还存活的对象作为内存遍历的根节点(GC Roots),比方说thread stack中的变量,JNI中的全局变量,zygote中的对象(class loader加载)等,然后开始对heap进行遍历。到最后,部分没有直接或者间接引用到GC Roots的就是需要回收的垃圾,会被GC回收掉。如下图蓝色部分。


内存泄漏指的是进程中某些对象(垃圾对象)已经没有使用价值了,但是它们却可以直接或间接地引用到gc roots导致无法被GC回收。无用的对象占据着内存空间,使得实际可使用内存变小,形象地说法就是内存泄漏了。下面分析一些可能导致内存泄漏的情景。

Android中常见的内存泄漏原因
1.使用static变量引起的内存泄漏

因为static变量的生命周期是在类加载时开始 类卸载时结束,也就是说static变量是在程序进程死亡时才释放,如果在static变量中引用了Activity那么这个Activity由于被引用,便会随static变量的生命周期一样,一直无法被释放,造成内存泄漏。

一般解决办法:
想要避免context相关的内存泄漏,需要注意以下几点:
- 不要对activity的context长期引用(一个activity的引用的生存周期应该和activity的生命周期相同)
- 如果可以的话,尽量使用关于application的context来替代和activity相关的context
- 如果一个acitivity的非静态内部类的生命周期不受控制,那么避免使用它;正确的方法是使用一个静态的内部类,并且对它的外部类有一WeakReference,就像在ViewRootImpl中内部类W所做的那样,使用弱引用private final WeakReference mViewAncestor;。

下面的代码存在内存泄漏的问题,非静态内部类的静态实例导致内存泄漏。

/**mDemo会获得并一直持有MemoryLeakActivity的引用。当MemoryLeakActivity销毁后重建,因为mDemo持有引用,无法被GC回收的,进程中会存在2个MemoryLeakActivity实例。所以,对于lauchMode不是singleInstance的Activity, 应该避免在activity里面实例化其非静态内部类的静态实例。*/public class MemoryLeakActivity extends AppCompatActivity{    private TextView view;    private static final String TAG = "MemoryLeakActivity";    static Demo mDemo;    @Override    protected void onCreate(@Nullable Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        view = new TextView(MemoryLeakActivity.this);        view.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));        view.setText("启动一个持有本对象的线程");        view.setTextSize(40);        view.setTextColor(Color.parseColor("#0000ff"));        setContentView(view);        mDemo = new Demo();        mDemo.run();    }    class Demo{        void run(){            Log.i(TAG, "run: ");        }    }}

解决方法:将Demo改成静态内部类
因为普通的内部类对象隐含地保存了一个引用,指向创建它的外围类对象。然而,当内部类是static的时,就不是这样了。嵌套类意味着: 1. 嵌套类的对象,并不需要其外围类的对象。 2. 不能从嵌套类的对象中访问非静态的外围类对象。

2.线程引起的内存泄漏

下面的代码存在内存泄漏的问题,启动线程的匿名内部类会持有MemoryLeakActivity.this的引用。如果线程还没有结束,Activity已经销毁那就会造成内存泄漏。

public class MemoryLeakActivity extends AppCompatActivity{    private TextView view;    private static final String TAG = "MemoryLeakActivity";    @Override    protected void onCreate(@Nullable Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        view = new TextView(MemoryLeakActivity.this);        view.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));        view.setText("启动一个持有本对象的线程");        view.setTextSize(40);        view.setTextColor(Color.parseColor("#0000ff"));        setContentView(view);        Runnable runnable = new Runnable() {            @Override            public void run() {                try {                    Thread.sleep(8000000L);                } catch (InterruptedException e) {                    e.printStackTrace();                }            }        };        new Thread(runnable).start();    }}

解决办法:
1.合理安排线程执行的时间,控制线程在Activity结束前结束。
2.将内部类改为静态内部类,并使用弱引用WeakReference来保存Activity实例 因为弱引用 只要GC发现了 就会回收它 ,因此可尽快回收。

将匿名内部类改成静态类,避免了Activity context的内存泄漏问题

/** * 示例通过将线程类声明为私有的静态内部类避免了 Activity context 的内存泄漏问题,但 * 在配置发生改变后,线程仍然会执行。原因在于,DVM 虚拟机持有所有运行线程的引用,无论 * 这些线程是否被回收,都与 Activity 的生命周期无关。运行中的线程只会继续运行,直到 * Android 系统将整个应用进程杀死*/public class MemoryLeakActivity extends AppCompatActivity{    private TextView view;    private static final String TAG = "MemoryLeakActivity";    @Override    protected void onCreate(@Nullable Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        view = new TextView(MemoryLeakActivity.this);        view.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));        view.setText("启动一个持有本对象的线程");        view.setTextSize(40);        view.setTextColor(Color.parseColor("#0000ff"));        setContentView(view);        new MyThread().start();    }    private static class MyThread extends Thread{        @Override        public void run() {            try {                Thread.sleep(8000000L);            } catch (InterruptedException e) {                e.printStackTrace();            }         }    }}

在Activity生命周期的onDestory中结束线程运行

/*** 除了我们需要实现销毁逻辑以保证线程不会发生内存泄漏。在退出当前* Activity 前使用 onDestroy() 方法结束你的运行中线程。*/public class MemoryLeakActivity extends AppCompatActivity{    private TextView view;    private static final String TAG = "MemoryLeakActivity";    private static boolean mRunnale = false;    private MyThread mThread;     @Override    protected void onCreate(@Nullable Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        view = new TextView(MemoryLeakActivity.this);        view.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));        view.setText("启动一个持有本对象的线程");        view.setTextSize(40);        view.setTextColor(Color.parseColor("#0000ff"));        setContentView(view);        new Thread(runnable).start();        new MyThread().start();    }    @Override    protected void onDestroy() {        super.onDestroy();        mThread.closeThread();    }    private static class MyThread extends Thread{        @Override        public void run() {            mRunnale = true;            while (true){                //TODO                Log.i(TAG, "run: do something");            }        }        public void closeThread(){            mRunnale = false;        }    }}
3.Handler的使用造成的内存泄漏

由于在Handler的使用中,handler会发送message对象到 MessageQueue中 然后 Looper会轮询MessageQueue 然后取出Message执行,但是如果一个Message长时间没被取出执行,那么由于 Message中有 Handler的引用,而 Handler 一般来说也是内部类对象,Message引用 Handler ,Handler引用 Activity 这样 使得 Activity无法回收。或者说Handler在Activity退出时依然还有消息需要处理,那么这个Activity就不会被回收。

解决办法:
依旧使用 静态内部类+弱引用的方式 可解决
例如下面的代码

public class MemoryLeakActivity extends AppCompatActivity{    private TextView view;    private static final String TAG = "MemoryLeakActivity";    private MyHandler mHandler;    @Override    protected void onCreate(@Nullable Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        view = new TextView(MemoryLeakActivity.this);        view.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));        view.setText("启动一个持有本对象的线程");        view.setTextSize(40);        view.setTextColor(Color.parseColor("#0000ff"));        setContentView(view);        mHandler = new MyHandler(this);        mHandler.sendEmptyMessage(0);    }    @Override    protected void onDestroy() {        super.onDestroy();        //第三步,在Activity退出的时候移除回调        mHandler.removeCallbacksAndMessages(null);    }    //第一步,将Handler改成静态内部类。    static class MyHandler extends Handler{        //第二步,将需要引用Activity的地方,改成弱引用。        private WeakReference mActivityRef;        public MyHandler(MemoryLeakActivity activity){            mActivityRef = new WeakReference(activity);        }        @Override        public void handleMessage(Message msg) {            super.handleMessage(msg);            MemoryLeakActivity mla = mActivityRef == null ? null : mActivityRef.get();            if(mla == null || mla.isFinishing()){                return;            }            //TODO            mla.view.setText("do something");        }    }}
4.资源未被及时关闭造成的内存泄漏

比如一些Cursor 没有及时close 会保存有Activity的引用,导致内存泄漏

解决办法:
在onDestory方法中及时 close即可

5.BitMap占用过多内存

bitmap的解析需要占用内存,但是内存只提供8M的空间给BitMap,如果图片过多,并且没有及时 recycle bitmap 那么就会造成内存溢出。

解决办法:
及时recycle 压缩图片之后加载图片

其中还有一些关于 集合对象没移除,注册的对象没反注册,代码压力的问题也可能产生内存泄漏,但是使用上述的几种解决办法一般都是可以解决的。

更多相关文章

  1. 关于 Android(安卓)进程保活,你所需要知道的一切
  2. android init进程--init.rc解析过程与执行
  3. Android(安卓)Framework - 开机启动 Init 进程
  4. Android(安卓)启动分析 init进程 init.rc
  5. 查看Android内存的8中方法
  6. 【Android(安卓)系统开发】 Android(安卓)系统启动流程简介
  7. 《Android(安卓)Security Internals》第一章安卓安全模型翻译
  8. AndroidManifest.xml的Service元素 android:process设置
  9. Android单个进程内存分配策略

随机推荐

  1. Android(安卓)五种不同样式Toast
  2. Android: min3D study
  3. Android(安卓)Seek自定义样式
  4. android 设置搜狗输入法为默认输入法
  5. android 3D gallery 并 判断当前选中项
  6. android读取日志demo
  7. Android(安卓)自定义日期和时间和星期的
  8. android 隐藏状态栏和导航栏
  9. android之Matrix
  10. Android(安卓)WebView demo