在日常面试过程中,经常会问到内存泄漏,内存溢出 数组越界 和ANR相关的问题,很多时候可能会分不清内存泄漏和内存溢出以及数组越界的区别,这里就简单地写下自己的看法,如果有什么理解上错误,欢迎大家指出。
1.内存泄漏
内存泄漏 Memory Leak ,对象已经在内存堆栈中分配了空间,但是出于某些原因已经没有使用的价值,但是还是直接或者间接被引用到,导致gc无法回收,如果是一次内存泄漏的危害及影响并不大,并不会导致程序出现闪退现象,而内存泄漏的次数多了就可能会引起内存耗尽 进而诱发内存溢出(OOM), 进而引起程序闪退,为什么说是可能会引起内存耗尽呢?因为java有GC机制,当你的内存紧张时会频繁触发GC,而GC是非常耗时的操作,会导致严重的卡顿体验上会极差,另外,当你的应用处于LRU即缓存列表中,即切换到后台,变为后台进程时,由于内存泄漏而消耗了更多内存,当系统资源不足而需要回收一部分缓存进程时,你的应用很大可能性会被被系统杀死。
内存泄漏常见的原因和如何解决
1.资源未关闭造成的内存泄漏
BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,避免这些资源将不会被回收,进而造成内存泄漏。
2.谨慎使用static关键字,万不能使用static修饰Activity context
static关键字修饰的对象占用内存,并且内存一般不会释放,只有内存不够用的时候才会释放静态内存(这里还有个隐患就是 可能会引起访问全局静态错误),如果使用static修饰Activity context会导致activity的所有组件对象都存入全局内存中,并且不会被回收。
3.单例造成的内存泄漏
单例的静态特性使得单例的生命周期和应用的生命周期一样长,这就说明了如果一个对象已经不需要使用了,而单例对象还持有该对象的引用,那么这个对象将不能被正常回收,这就导致了内存泄漏。
4.内部类造成的内存泄漏
尤其是Activity的内部类的生命周期,尽量使用静态内部类代替内部类,如果内部类需要访问外部类的成员,可以用“静态内部类+弱引用”代替;内部类的生命周期不应该超出外部类,外部类结束前,应该及时结束内部类生命周期(停止线程、AsyncTask、TimerTask、Handler消息等,移除类变量或长生命周期的线程对Callback、listener等的强引用)
更多避免内存泄漏要注意的:
1.属性动画在Activity销毁前记得cancel
2.文件流、Cursor等资源用完及时关闭;
3.Activity销毁前WebView的移除和销毁;
4.使用别人的方法(尤其是第三方库),遇到需要传递context时尽量使用ApplicationContext,而不要轻易使用Activity context,因为你不知道别人的代码内部会不会造成该context的泄漏.
常见的分析手段
借助 内存泄漏检测工具LeakCanary 和 内存分析工具——MAT
2.内存溢出 OOM
系统会给每个APP分配内存也就是Heap Size值。当APP占用的内存加上我们申请的内存资源超过了Dalvik虚拟机的最大内存时就会抛出的Out Of Memory异常。
内存溢出 OOM常见的原因和如何解决
1.大量内存泄漏
上面的造成内存泄漏原因都有可能导致内存溢出,因为大量内存泄漏会导致内存耗尽从而引发内存溢出,根本解决方案就是解决内存泄露的问题,如何解决如上
2.频繁申请内存而不释放无用内存
有些代码并不造成内存泄露,但是它们,或是对没使用的内存没进行有效及时的释放,或是没有有效的利用已有的对象而是频繁的申请新内存,对内存的回收和分配造成很大影响的,容易迫使虚拟机不得不给该应用进程分配更多的内存,造成不必要的内存开支从而诱发内存泄漏,解决方案尽量避免重复申请内存空间以及及时释放无用的内存
3.Bitmap使用不当
众所周知Bitmap的是比较耗内存的,Bitmap对象在不使用时,我们应该先调用recycle()释放内存,然后才它设置为null.,在使用过程中尽量采用低内存占用量的编码方式,如Bitmap.Config.ARGB_4444比Bitmap.Config.ARGB_8888更省内存
4,构造Adapter时,没有复用convertView(即没有使用缓存的 convertView)
如果不复用convertView会导致每次getview()都会重新实例化一个新的View对象,导致内存增加长时间滑动会导致内存耗尽出现OOM ,解决方案复用convertView
常见的分析手段
借助 内存泄漏检测工具LeakCanary 和 内存分析工具 MAT
3 数组越界
更多的时候事叫 下标越界 和内存溢出的概念很相似,一不小心就会混淆对比来解释下加深记忆
数组越界: 在引用数组元素时,使用的下标超过了该数组下标的应有范围
内存溢出: 在初始化数组(给数组元素赋值)时,初始化元素的个数超过了数组定义时元素的个数。
数组越界是在引用是出现问题 内存溢出是在初始化时出现问题
出现的场景以及解决方案
1.多线程操作同一个数组
多线程操作同一个数组时,同时进行删除和引用可能会导致数组越界,可以在多线程使用是使用同步锁或者使用线程安全的数组对象。
2.引用下标超出数组大小
这种情况就相对简单,可以在判断引用的下标是否大于数组长度-1 ,一般数组的下标是从0开始的 如果大于的话求放弃引用
4.应用程序无响应 ANR
ANR全称:Application Not Responding,也就是应用程序无响应。Android系统中,ActivityManagerService(简称AMS)和WindowManagerService(简称WMS)会检测App的响应时间,如果App在特定时间无法相应屏幕触摸或键盘输入时间,或者特定事件没有处理完毕,就会出现ANR。
ANR的四种类型
InputDispatching Timeout:5秒内无法响应屏幕触摸事件或键盘输入事件
BroadcastQueue Timeout:在执行前台广播(BroadcastReceiver)的onReceive()函数时10秒没有处理完成,后台为60秒。
Service Timeout:前台服务20秒内,后台服务在200秒内没有执行完毕。
ContentProvider Timeout:ContentProvider的publish在10s内没进行完。
ANR常见的原因和如何解决
1.主线程进行耗时操作(数据库操作,耗时的网络访问等)
在主线程中进行耗时操作会导致主线程卡顿 出现超过5秒无响应 就会出现ANR,应该尽量避免在主线程中进行耗时操作,把耗时操作放到子线程中
2.多线程持死锁导致的ANR
出现这种情况要去具体分析trace文件分析具有哪个线程死锁导致主线block 然后分析向上分析出现问题的原因
3.BroadcastReceiver的onReceive进行耗时操作
在执行前台广播(BroadcastReceiver)的onReceive()函数时10秒没有处理完成,后台为60秒。如果超时就会出现anr ,如果要执行耗时操作时应启动一个service,将耗时操作交给service来完成,具体问什么不能再onReceive开一个子线程进行处理 详见为什么不能在BroadcastReceiver中开启子线程
4.主线程中加载过大数据和图片
有时候我们需要加载很多高清无码的大图,或者加载特别大的数据时也会出现anr因为这些都是耗内存的操作,但是更新ui的操作又不能放到子线程中,懒汉方法就压缩图片,可以在服务端压缩也可以在客户端压缩,当然Android如果是大数据可以分段加载。