OOM(内存溢出):当前占用的内存加上申请的内存超过了虚拟机的最大内存限制就会抛出OOM异常
内存抖动:大量临时对象被创建,又很快被回收,内存抖动会造成GC压力。inPurgable改为inBitmap、避免在onDraw中创建对象
内存知识:Android提供了两个查看内存的工具:procstats、meminfo,使用方法:adb shell dumpsys procstats
VSS is the total accessible address space of a process.This size also includes memory that may not be resident in RAM like malloc that have been allocated but not written to. VSS is of very little use for determing real memory usage of a process.
RSS is the total memory actually held in RAM for a process.RSS can be misleading, because it reports the total all of the shared libraries that the process uses, even though a shared library is only loaded into memory once regardless of how many processes use it. RSS is not an accurate representation of the memory usage for a single process.
PSS differs from RSS in that it reports the proportional size of its shared libraries. if three processes all use a shared library that has 30 pages, that library will only contribute 10 pages to the PSS that is reported for each of the three processes. PSS is a very useful number because when the PSS for all processes in the system are summed together, that is a good representation for the total memory usage in the system.
USS is the total private memory for a process. that memory that is completely unique to that process.USS is an extremely useful number because it indicates the true incremental cost of running a particular process. When a process is killed, the USS is the total memory that is actually returned to the system. USS is the best number to watch when initially suspicious of memory leaksin a process.
降低内存:裁剪图片(InJustDecodeBound、inSampleSize)、降低像素(ALPHA_8、ARGB_4444、ARGB_8888、RGB_565)、主动回收Bitmap(Bitmap.recycle())、使用inBitmap属性、捕获OOM
内存泄漏指的是进程中某些对象已经没有使用价值了,但是它们却可以直接或间接地引用到gc roots导致无法被GC回收
内存泄漏危害:频繁GC,降低应用性能、严重会导致OOM(https://www.jianshu.com/p/65f914e6a2f8)
内存泄漏场景:
1、单例和静态变量造成的内存泄漏(应持有ApplicationContext;若必须为Activity,应改为弱引用)
2、匿名内部类和非静态内部类造成的内存泄漏(Handler内存泄漏、AsyncTask内存泄漏、Thread内存泄漏)
3、静态集合类、注册服务
判断内存泄漏的方法:DDMS(触发GC,观察内存是否将到初始水平)、adb shell dumpsys meminfo pid、MAT(搜索大对象,比如Activity对象超过1个)、LeakCanary
LeakCanary原理:LeakCanary通过registerActivityLifecycleCallbacks监听activity事件,在onActivityDestoryed中自动调用RefWatcher.watch方法来检测Activity内存泄漏,如果想检测Service也可以在相应时机手动调用RefWatcher.watch方法,在watch方法中,创建了监控对象的弱引用,并传入了弱引用队列。手动触发GC(Runtime.getRuntime().gc()),利用弱引用被回收会被放入引用队列的机制,将引用队列里的被回收对象出列,并从Set集合中移除对应的key值。如果Set集合中监控对象的key值没有被移除,说明监控对象没有被放入引用队列,即没有被回收,发生了内存泄漏,接着dump内存,通过MAT(memory analyzer tool)进行深入分析
RefWatcher refWatcher = null;
@Override
public void onCreate() {
super.onCreate();
refWatcher = LeakCanary.install(this);
}
public class WeakReference<T> extends Reference<T> {
public WeakReference(T referent) {
super(referent);
}
public WeakReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
}
ANR产生原因:
主线程5秒内没有响应输入事件
主线程在执行BroadcastReceiver的onReceive函数时10秒内没有执行完毕
BroadcastReceiver的onReceive函数运行在主线程中,当这个函数超过10秒钟没有返回就会触发ANR,不过对这种情况的ANR系统不会显示对话框提示,仅是输出日志
主线程在执行Service的各个生命周期函数时20秒内没有执行完毕
Service的各个生命周期函数也运行在主线程中,当这些函数超过20秒钟没有返回就会触发ANR。同样对这种情况的ANR系统也不会显示对话框提示,仅是输出日志
三种ANR发生时都会在日志中输出错误信息,另外系统各个进程的函数堆栈信息都会输出到/data/anr/traces.txt文件中,由于向 /data/anr/traces.txt 文件中写入信息耗时较长,从ANR触发到弹出ANR提示框一般在10s左右(不同rom时间不同)
1、系统捕获到ANR发生
2、Process 依次向本进程及其他正在运行的进程发送Linux信号量
3、进程接收到Linux信号量,并向 /data/anr/traces.txt 中写入进程信息
4、Log日志打印ANR信息
5、进程进入ANR状态, 弹出ANR提示框
6、提示框消失,进程回归正常状态
当APP发生ANR时,并不会像发生Java异常时有回调接口,因此我们需要主动监控ANR何时发生并获取ANR相关信息,两种监控方式,通过FileObserver,优点是基于linux底层,没有性能消耗,缺点是5.0以上系统基本不好用。第二种方式是通过看门狗,每隔5秒向主进程发送消息,4秒是概率算出来的,anr处理时间一般在10s左右,4秒发送一个信号,也可能会漏掉8秒以下的anr
引起ANR的根本原因,总的来说可以归纳为两类:
应用进程自身引起的,例如:主线程执行耗时操作(IO、网络请求、复杂计算)、挂起、死锁、死循环;应用进程的其他线程的CPU占用率高,使得主线程无法抢占到CPU时间片
其他进程间接引起的,例如:当前应用进程进行进程间通信请求其他进程,其他进程的操作长时间没有反馈;其他进程的CPU占用率高,使得当前应用进程无法抢占到CPU时间片
执行在主线程的操作:Activity、Service、BroadcastReceiver生命周期回调、没有使用子线程的Looper的Handler、AsyncTask的回调中除了doInBackground
如何分析ANR
AMS请求kernel向进程发送SIGNAL_QUIT请求,你可以使用命令达到相同的目的:adb shell kill -3 5033
发生ANR时Android为我们提供了两种“利器”:日志信息(CPU使用率和major页错误次数)、traces文件
当前发生ANR的应用进程被第一个添加进firstPids集合中,所以会第一个向traces文件中写入信息。反过来说,traces文件中出现的第一个进程正常情况下就是发生ANR的那个进程,每次发生ANR时都会删除旧的traces文件,重新创建新文件
anr上报时机:通过FileObserver监控data/anr目录的CLOSE_WRITE状态拿到最新的traces.txt文件,通过正则表达式获取第一个进程的线程栈信息,在通过ActivityManagerService.getProcessesInErrorState() 方法获取到ProcessErrorStateInfo列表,获取到NOT_RESPONDING状态的Error
如何避免ANR:
在主线程中所做的任何耗时的操作都有可能造成ANR,因此在主线程中执行的任何函数所做的工作都应该尽可能的少。sharePreferences、文件、网络、数据库,以及诸如位图变换的一些耗时的操作,都应该放在子线程中完成。主线程不需要等待子线程的执行,主线程应该创建一个与其绑定的Handler对象,子线程执行完毕后通过Handler通知主线程
sharePreferences:不要存放JSON和HTML,不要存放大的key和value,全量IO会引起界面卡顿、全部读取也会占用大量内存。读取频繁的key和不易变动的key尽量不要放在一起。不要乱edit和apply,主线程使用apply,子线程使用commit,尽量批量修改一次提交。不要指望用这货进行跨进程通信
创建子线程的方式有很多,Android中提供了很多相关的API,例如HandlerThread、AsyncTask、AsyncQueryHandler等,当然也可以创建简单的线程或线程池。不过需要注意的是,不要制造太多的“野”线程,例如在Android原生代码中经常会见到在某个函数中new了一个Thread对象,调用其start函数启动后就不再管理了。笔者对这种写法并不认同,这种写法的优点是避免了子线程对某些对象的强引用,以免内存泄漏,但是反而有潜在的风险会造成“线程泄漏”,也就是说可能会多次执行相同的操作创建大量的子线程,而这些子线程很可能由于某种原因被阻塞而都无法正常退出,大量的线程本身就会占用内存和CPU,抢占临界资源,而且执行的多是重复的操作。所以笔者建议将子线程“管理”起来,无论是用标志位还是用成员变量,总之不要让线程随意的被创建,用有限数量的线程或线程池处理所有的请求,可以用Handler将请求队列化,去除重复的请求减少资源浪费,同时应该在适当的时候(例如Activity销毁时)考虑停止子线程,避免不必要的操作和内存泄漏。
BroadcastReceiver通常是用来在后台执行一些小型的、琐碎的工作,例如保存程序设置。不要在BR中执行需要长时间运行的操作,这些操作应该放到Serivce中
通常人们感知界面有停顿感的时间需要100到200毫秒,所以为了给用户更好的体验,在避免ANR的基础上更进一步来消除停顿感,如果应用程序的初始化过程比较耗时,可以在初始化时显示一个过场动画或者图片,也可以先快速的显示主界面然后再异步的加载初始化数据
UI卡顿:60fps(60帧每秒) -> 16ms
UI卡顿原因:轻微耗时操作、Layout过于复杂、overdraw(过度绘制,UI布局中存在大量重叠部分)、requestLayout、GC过于频繁、逻辑冗余、滑动时没有暂停加载
冷启动时间:从应用启动(创建进程)开始计算,到完成视图的第一次绘制为止