现在快手字节跳动等公司都在大量招人,薪资优厚,但是想进去却没那么简单,面过的人都知道,这些公司的面试官巴不得把你会的东西都给你挖出来,所以要深入复习知识点,让自己耐问一点。一下是针对内存泄露真实面试过程:
1.什么是内存泄漏
内存泄漏是指程序申请内存之后,无法释放已经申请的内存,也就是该回收的对象不能被回收。一般是持有引用的对象生命周期比该引用生命周期长导致。
2.哪些场景导致内存泄漏
资源对象没有关闭导致内存泄漏,比如广播没有反注册,EventBus
没有反注册,文件流没有关闭,数据库指针没有关闭,bitmap用完之后没有回收。
static修饰的成员变量或者单例模式,比如static修饰的context,由于被修饰的对象生命周期和app一样长,可能会导致activity无法被回收。
在非静态内部类中持有静态实例。因为非静态累默认持有外部类的实例,当持有静态实例的时候会导致内部类生命周期和app一样长,从而导致外部类的生命周期也和app一样长而无法被回收。只要把非静态内部类改成静态内部类即可。
3.内存溢出会有什么影响
一次两次内存泄漏出不会造成什么影响,但是内存泄漏多了会导致系统不断发生gc从而导致程序卡顿,同时内存泄漏多了可能会导致系统分配的内存不够用而发生内存溢出crah。
4.如何检测内存泄漏
- mat分析内存
- 集成
leakcanery
检测内存泄漏 -
androidstudio
lint静态代码检测内存泄漏
5.请描述mat分析内存的过程
在程序运行的时候再as上打开profiler
,然后选择memory,在手机上操作,比如关闭对应的activity,然后点击as上的GC
,点击dump,一般来说as就可以自动分析hprof
文件,整理出对应的内存信息,可以选择按包排序,然后选择我们的程序,看看那些对象还在使用内存,如果应该被回收的对象却还在使用内存,说明已经发生内存泄露了。
然后如果不想在AS上分析,也可以导出hprof
文件,去eclipse中找到mat进行分析,但是mat不能识别hprof
文件需要使用hprof-conv
命令进行转换,命令是hprof-conv
源文件 输出文件,之后就可以使用mat进行内存分析了。
其中最主要的两个工具是Histogran
可以看到不同类型buffer数量和占用内存大小,Dominator Tree
把对象按照内存大小进行排序,然后我们可以通过merge shortest paths to gc roots
,然后选择引用关系,就可以看到究竟是哪内存泄露了。
6.leakecanery
的原理了解吗
如果我们想监听一个对象是否被回收,可以采用weakreference
和referencequeue
的结合的方式进行。我们在构造weakreference
的时候传入一个referencequeue
实例,当weakreference
引用的对象被回收的时候,他会被存在pending内,然后会有个线程无线循环读取这个pending队列,这个weakreference
会被添加到这个referencequeue
中,这时候我们可以查找referencequeue
队列,如果里面有这个weakreference
,则说明对应的对象被回收了。
在leakcanery
初始化的时候会注册app的lifecycleCallbacks
,当activity执行ondestroy
的时候leakcanery
会收到回调,并拿到对应的activity的引用,此时会调RefWatcher
的watcher方法,在这个方法里会通过AndroidWatcherExcetor
启动一个Runnable
任务,在这个任务中会用构建keyedweakreference
,并传入activity对象和referencequeue
的实例,并且这个任务会延迟5秒之后再执行。
当activity被回收时,keyedweakreference
会被加入referencequeue
中,如果发生内存泄漏,则referencequeue
无法找到keyedweakreference
对象,此时可以判断发生了内存泄漏。所以在这个任务中,会调用ensureGone
方法去referencequeue
查找是否有keyedweakreference
对象,如果有则说明发生了内存泄露,activity没有被回收。则会再次调用GC方法再回收一次,如果此时referencequeue
中仍fan没有keyedweakreference
的对象,则说明肯定存在内存泄露,则通过heapDumper
分析prof文件,并通过heampdumplister
回调结果到通知栏。
7.leakecanery
具体是如何判断有没有发生内存泄露的呢?
刚才说过,当WeakReference
里传入ReferenceQueue
的时候,如果WeakReference
内持有的对象引用被回收,则会把WeakReference
对象添加到ReferenceQueue
队列中。利用这个原理,leakcanary
在监听到activity执行onDestroy
的时候会构建一个keyedWeakReference
实例,这个keyedWeakReference
继承自weakReference
,里面包含key和name字段
构建keyedWeakReference
实例的时候会随机生成一个key,传进去,也就是这个弱引用对象和这个key是绑定的。同时,会把这个key存入到Set retainedKeys
中。当执行ensureGone
方法的时候会先去keyedWeakReference
队里中查找WeakReference
对象,如果存在,则说明已经回收成功,则从retainedKeys
中移除对应的key,然后再检验retainedKeys
中是否还存有该WeakReference.key
,如果不存在了,则说明已经成功回收,如果还存在,则说明没有成功回收,此时出发一次GC,再走一遍刚才的流程,如果仍然存在key,则说明确定发生内存泄露了。上面讲到,检测内存泄露的任务延迟了5秒钟,已经给足了回收的时间,如果此时都不能被回收掉,说明activity肯定发生了内存泄露。
8.检测内存泄露的任务发生在哪个线程?
通过
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler())
等待主线程空闲的时候发送到子线程中,并延迟5秒。
9.什么叫内存溢出
内存溢出就是程序在申请内存的时候系统可用内存或者系统分配的内存不够了导致发生oom异常。如果内存溢出得多了就会导致内存溢出。
10.内存溢出一般发生在就JVM哪个区
JVM中程序技计数器,虚拟机栈,本地方法栈都是随线程生,随线程灭,帧栈随方法进入和退出做了入栈和出栈处理,实现了内存自动清理,所以内存溢出一般会发生在堆区和方法区,其中堆区用于堆区用于存放对象的实例,是垃圾收集器的主要管理区域,并且分代管理对象;方法区一般存放虚拟机加载的类信息,常量,静态变量,编译后的代码和数据,GC主要回收方法区的常量和类卸载。这两个区域都可能发生内存溢出.
11.你知道GC的算法吗
- 1、GC的对象
引用计数为0的对象,每个对象都有个引用计数属性,新增一个引用的时候加一,引用释放的时候减一,当引用数为0的时候该对象可以被GC回收,但是无法解决对象相互循环引用的问题
不可达对象,从GC roots开始向下搜索,搜索所走过的路径成为引用链,当对象到GC roots没有引用链的时候为不可达对象,则可以回收 - 2、GC出发的时机为调用
System.gc
方法和系统根据情况自动进行GC。 - 3、GC算法:
记清除算法
该算法分为两个阶段,第一阶段为标记,为每个对象更新标记位,检查对象是否死亡;第二阶段是清除阶段,清除死亡对象,GC操作。
优点: 每个对象只要能找到一个引用就可以判定为活着,且不移动对象的位置。
缺点: 每次标记都需要遍历一遍,每次清除也都需要再扫描一遍,算法复杂度较高,没有移动对象,会导致一些碎片空间无法利用标记压缩算法,该算法是标记清除算法的改进,他也会标记所有的对象的死亡和存活状态,但是不会直接清除,而是会把所有存活的对象整理到一起放到另一个空间然后清除剩余的对象,这样就不会想清除标记算法那样产生大量的碎片空间。缺陷是如果存活的对象过多会产生很多复制操作,算法效率较低
复制算法,使用两块一模一样的空间,每次只是用一块空间,当内存满了之后会把存活的对象移到另一块空间,然后清空之前的内存,如此循环下去。区别在于复制算法移动的不是同一区域,而是都复制移动到另一区域。
优点: 是实现简单,不产生碎片。
缺点: 是每次运行都会有一般的空间被浪费。分代收集算法,根据生存周期,把堆分为新生代和老年代。在新生代区对象生存周期短,每次都有大量的对象死去,所以就可以使用复制算法,但在老年代里的对象存活率高,没有额外的空间进行分配,所以可以使用标记清理算法和标记整理算法。其中新生代又分为Eden区、from区、to区;系统创建对象的时候再Eden区域操作,当这个区满了发生一次youngGC,然后把剩下的对象复制到from区,然后继续在Eden区域创建对象,当满了之后在发生一次youngGC,把Eden区域和from区域剩下的对象复制到to区域,然后如此重复,许多多想在from和to之间反复移动,达到一定阈值之后会把剩下还在使用的对象复制到老年代,当老年代也满之后会发生一次fullGC全量回收,但是这个fullGC 太频繁会影响系统,所以需要合理设置老年代的大小。
网友评论