转载请注明出处:http://blog.csdn.net/smartbetter/article/details/65442706
怎样才能写出高性能的应用程序,如何避免程序出现OOM,或者当程序内存占用过高的时候该怎么样去排查。这些问题对于一个优秀的应用程序应当处理得恰到好处。为此,我也阅读了不少Android官方给出的性能优化建议。本篇将系统的从 Android的内存管理方式 到 App内存优化方法,最后到OOM问题优化,系统的讲解内存优化的一些方案、见解,帮助大家能够写出更加出色的应用程序,避免OOM。
建议阅读的官方文档:https://developer.android.google.cn/topic/performance/memory.html
1.Android的内存管理方式
我们知道Android系统是多任务系统,通过分时复用的方式,多个应用可以同时在一个手机上运行。
我们打开终端,输入:
$ adb shell // 进入安卓底层Linux系统的命令
$ ps // 查看系统里面进程的命令
USER:用户, PID:进程id, PPID:父进程id, VSIZE:进程虚拟地址空间大小, RSS:进程正在使用的物理内存大小, WCHAN:进程处于休眠状态时在内核中的地址, PC:program counter, NAME:进程名称.
$ dumpsys meminfo com.android.bluetooth // 查看指定进程相关信息
1.Android系统内存分配与回收方式
一个App通常就是一个进程对应一个虚拟机。
GC只在Heap剩余空间不够时才触发垃圾回收,而且GC触发时,所有的线程都会被暂停(如果GC时间比较长,还有可能出现内存抖动的现象)。
2.App内存限制机制
每个App分配的最大内存限制,随不同设备而不同。系统分配的大小肯定是够用的,如果你的App出现OOM,往往是你的App优化做的不是很好。
其中吃内存比较厉害就是图片了。
Android上App的运行是有内存限制的,OOM导致App崩溃。
我们可以通过代码的方式查看一下手机的内存限制和最大内存限制:
ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
Log.i("JAVA", "内存限制:" + manager.getMemoryClass() + "M");
Log.i("JAVA", "最大内存限制:" + manager.getLargeMemoryClass() + "M");
3.切换应用时后台App清理机制
多个App切换的时候使用的是 LRU Cache(其中是使用LRU算法进行清理排序的。LRU算法:最近使用的排在最前面,最少可能的被清理掉)。
当具体清理的时候,系统还会发出 onTrimMemory() 的回调,随着系统内存有变化的时候发出回调给各个应用,通知系统的内存情况,然后我们的App可以做相应处理,把App中不用的内存尽快清理掉,这样你的App的占用就相对小一点,系统在查看后台App的时候发现你这个App内存占用比较少,那么在清理的时候就会小一点。
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
Log.i("JAVA", "level:" + level);
}
}
4.监控内存的几种方法
我们可以通过代码的方式监控内存:
Runtime runtime = Runtime.getRuntime();
Log.i("JAVA", "总内存:" + runtime.totalMemory() * 1.0f/(1024*1024) + "M");
Log.i("JAVA", "空闲内存:" + runtime.freeMemory() * 1.0f/(1024*1024) + "M");
Log.i("JAVA", "可申请最大内存:" + runtime.maxMemory() * 1.0f/(1024*1024) + "M");
我们还可以通过 Android Studio 的 Android Monitors工具 方便的监控内存:
除此之外,Android Studio还提供了Android Device Monitor工具,可用于整体的监控内存:
我们先选住应用程序,再点击 Heap 按钮:
然后点击 Cause GC 按钮:
此时Android Device Monitor就为我们展示了当前整体的内存情况:
其中我们主要看的就是 data object 和 class object 这两个,如果不停的运行,这两个也不停的变大,那么很有可能内存泄漏。
2.App内存优化方法
1.数据结构优化
1)频繁字符串拼接用 StringBuilder,字符串通过”+”的方式拼接,会产生中间字符串内存块;
2)ArrayMap、SparseArray 替换 HashMap,内存使用更少,数据量大了效率更高;
3)内存抖动(监控内存时发现内存情况呈现 锯齿形),一般是代码设计的时候变量使用不当引起的;
4)再小的Class也要耗费 0.5KB;
5)HashMap每一个entry需要额外占用 32B。
2.对象复用
1)复用系统自带的资源;
2)ListView/GridView 的 ConvertView 复用;
3)避免在 onDraw方法 里面执行对象的创建,把对象创建放在外面。
3.避免内存泄漏
内存泄漏,由于代码有瑕疵,导致这块内存发生内存泄漏,虽然是停止不用了,但是依然被其他东西引用着,使得GC没法对它回收。
1)内存泄漏会导致剩余可用 Heap 越来越少,频繁触发GC,尤其是Activity泄漏;
2)引用上下文选用 Application Context 而不是 Activity Context;
3)注意 Cursor对象 及时关闭。
3.OOM问题优化
1.OOM问题分析
OOM的绝大部分场景发生在图片。
2.强引用、软引用
强引用:GC绝不会回收它,JVM宁愿抛出OutOfMemoryError错误,一般new出来的对象都是强引用。
// 如果是在Activity中创建则生命周期同Activity,如果在方法中创建则生命周期同方法。
// String str; 时就已经创建,new是进行实例化的(分配对象内存,并将该内存初始化为缺省值)。
String str = String.valueOf(Math.random());
软引用:当内存空间不足,GC会回收这些对象的内存,使用软引用构建敏感数据的缓存。
// 生命周期同强引用,只是当内存空间不足,GC会回收这些对象的内存。运行过程中可能会出现softref为null的情况。
SoftReference<String> softref = new SoftReference<String>(String.valueOf(Math.random()));
弱引用:在GC线程扫描内存区域的过程中,不管当前内存空间足够与否,都会回收内存,使用弱引用 构建非敏感数据的缓存。
虚引用:在任何时候都可能被垃圾回收,虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列联合使用,虚引用主要用来跟踪对象 被垃圾回收的活动。
3.Activity onTrimMemory()回调方法
@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
}
1)UI 不可见时释放资源:
在 onStop 中关闭网络连接、注销广播接收器、释放传感器等资源;
在 onTrimMemory() 回调方法中监听 TRIM_MEMORY_UI_HIDDEN 级别的信号,此时可在 Activity 中释放 UI 使用的资源,大符减少应用占用的内存,从而避免被系统清除出内存。
2)内存紧张时释放资源:
运行中的程序,如果内存紧张,会在 onTrimMemory() 回调方法中接收到以下级别的信号:
TRIM_MEMORY_RUNNING_MODERATE:系统可用内存较低,正在杀掉LRU缓存中的进程。你的进程正在运行,没有被杀掉的危险。
TRIM_MEMORY_RUNNING_LOW:系统可用内存更加紧张,程序虽然没有被杀死的危险,但是应该尽量释放一些资源,以提升系统的性能。
TRIM_MEMORY_RUNNING_CRITICAL:系统内存极度紧张,而LRU缓存中的大部分进程已被杀死,如果仍然无法获得足够的资源的话,接下来会清理掉LRU中的所有进程,并且开始杀死一些系统通常会保留的进程,比如后台运行的服务等。
当程序未在运行,保留在 LRU 缓存中时, onTrimMemory() 回调方法中接收到以下级别的信号:
TRIM_MEMORY_BACKGROUND:系统可用内存低,而你的程序处在LRU的顶端,不会被杀死,但是此时应释放一些程序再次打开时比较容易恢复的 UI 资源。
TRIM_MEMORY_MODERATE:系统可用内存低,程序处于LRU的中部位置,如果内存状态得不到缓解,程序会有被杀死的可能。
TRIM_MEMORY_COMPLETE:系统可用内存低,你的程序处于LRU尾部,如果系统仍然无法回收足够的内存资源,你的程序将首先被杀死,此时应释放无助于恢复程序状态的所有资源。
注意:该 API 在版本 14 中加入,旧版本的onLowMemory() 方法,大致相当于 onTrimMemory(int level) 中接收到 TRIM_MEMORY_COMPLETE 级别的信号。
尽管系统主要按照 LRU 中顺序来杀进程,不过系统也会考虑程序占用的内存多少,那些占用内存高的进程有更高的可能性会被首先杀死。
4.优化OOM问题的方案
1)注意临时 Bitmap 对象的及时回收
2)避免 Bitmap 的浪费
3)try catch 某些大内存分配的操作
4)加载 Bitmap(安卓手机上一般一个像素用4个字节来表示(ARGB)):缩放比例、解码格式、局部加载、软引用