【Android高阶】性能优化问题之内存十二问

1. 为什么官方建议别在onDraw创建对象?

每创建一个对象,就会有一块内存分配给它;每分配一块内存,程序的可用内存也就少一块;当程序被占用的内存达到一定临界程度,GC 也就是垃圾回收器(Garbage Collector)就会出动,来释放掉一部分不再被使用的内存。Android 里的 View.onDraw 方法在每次需要重绘的时候都会被调用,这就意味着,如果你在 onDraw 里写了创建对象的代码,在界面频繁刷新的时候,你就也会频繁创建出一大批只被使用一次的对象,这就会导致内存占用的迅速攀升;然后很快,可能就会触发 GC 的回收动作,也就是这些被你创建出来的对象被 GC 回收掉。垃圾内存太多了就被清理掉,这是 Java 的工作机制,这不是问题。问题在于,频繁创建这些对象会造成内存不断地攀升,在刚回收了之后又迅速涨起来,那么紧接着就是又一次的回收,对吧?这么往复下来,最终导致一种循环,一种在短时间内反复地发生内存增长和回收的循环。

这种循环往复的状态就像是水波纹的颤动一样,它的专业称呼叫做 Memory Churn,Android 的官方文档里把它翻译做了内存抖动。

 

2.内存抖动为什么会导致程序卡顿与OOM?

CMS垃圾回收器(CMS是老年代垃圾收集器)老年代是标记-清除算法:不会移动存活的对象,会产生内存碎片。
像上述图中的内存,虽然有很多内存可用,但却是不连续的,如果申请连续的10个字节(假设图中一个空格代表一个字节)的内存就会产生OOM,因为没有连续的10个字节的可用内存。
比如申请bitmap时就很可能产生OOM。

 

3.内存泄漏产生的原因与排查方案?

  1. 非静态内部类以及非静态匿名内部类持有对外部类的引用。
  2. Activity里的Handler:
  3. 单例类传入context
  4. 注册/反注册未成对使用引起的内存泄漏(addListener(this))
  5. 资源未关闭造成的内存泄漏
  6. 集合对象没有及时清理引起的内存泄漏
移除掉所有的静态引用。

考虑用 EventBus 来解耦 Listener。

记着在不需要的时候,解除 Listener 的绑定。

尽量用静态内部类。

对于使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。

4.GC是怎么回收对象的?怎么确定对象是否可被回收?

  1. 如何判断对象已“死”:
    1. 引用计数法:给对象增加一个引用计数器,每当有一个地方引用它时,计数器就+1;当引用失效时,计数器就-1;任何时刻计数器为0的对象就是不能再被使用的,即对象已“死”。
      但是!无法解决对象的循环引用问题。
    2. 可达性分析算法:通过一系列称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索走过的路径称为“引用链”,当一个对象到 GC Roots 没有任何的引用链相连时(从 GC Roots 到这个对象不可达)时,证明此对象不可用。


      在Java语言中,可作为GC Roots的对象包含以下几种:

      1. 虚拟机栈(栈帧中的本地变量表)中引用的对象。
      2. 方法区中静态属性引用的对象
      3. 方法区中常量引用的对象
      4. 本地方法栈中(Native方法)引用的对象
  2. Java内存回收方式:
    1. JVM内存结构由程序计数器、堆、栈、本地方法栈、方法区等部分组成。
      1. 程序计数器:几乎不占有内存。用于取下一条执行的指令。
      2. 栈:每个线程执行每个方法的时候都会在栈中申请一个栈帧,每个栈帧包括局部变量区和操作数栈,用于存放此次方法调用过程中的临时变量、参数和中间结果。
      3. 本地方法栈:用于支持native方法的执行,存储了每个native方法调用的状态
      4. 方法区: 存放了要加载的类信息、静态变量、final类型的常量、属性和方法信息。JVM用永久代(PermanetGeneration)来存放方法区,(在JDK的HotSpot虚拟机中,可以认为方法区就是永久代,但是在其他类型的虚拟机中,没有永久代的概念,有关信息可以看周志明的书)可通过-XX:PermSize和-XX:MaxPermSize来指定最小值和最大值。
      5. 堆:所有通过new创建的对象的内存都在堆中分配,其大小可以通过-Xmx和-Xms来控制。堆被划分为新生代和老年代。
        1. 新生代又被进一步划分为Eden和Survivor区,最后Survivor由FromSpace和ToSpace组成,可以理解为Edin+SurvivorFrom+SurvivorTo(8:1:1)组成:(复制算法
          1. 当Eden区满的时候,会触发第一次Minor gc,把还活着的对象拷贝到Survivor From区;

          2. 当Eden区再次出发Minor gc的时候,会扫描Eden区和From区,对两个区域进行垃圾回收,经过这次回收后还存活的对象,则直接复制到To区域,并将Eden区和From区清空。

          3. 当后续Eden区又发生Minor gc的时候,会对Eden区和To区进行垃圾回收,存活的对象复制到From区,并将Eden区和To区清空

          4. 部分对象会在From区域和To区域中复制来复制去,如此交换15次(由JVM参数MaxTenuringThreshold决定,这个参数默认是15),最终如果还存活,就存入老年代。

        2. 针对老年代的特点,提出了一种称之为“标记-整理算法”。标记过程仍与“标记-清除”过程一致,但后续步骤不是直接对可回收对象进行清理,而是让所有存活对象向一端移动,然后直接清理掉端边界以外的内存。

5.OOM到底是如何产生的呢?

当JVM因为没有足够的内存来为对象分配空间并且垃圾回收器也已经没有空间可回收时,就会抛出这个error。

为什么会没有内存了呢?原因不外乎有两点:

1)分配的少了:比如虚拟机本身可使用的内存(一般通过启动时的VM参数指定)太少。

2)应用用的太多,并且用完没释放,浪费了。此时就会造成内存泄露或者内存溢出。

 

6.leakCanary是如何发现OOM的?

  1. RefWatcher.watch() 创建一个 KeyedWeakReference 到要被监控的对象。
  2. 然后在 AndroidWatchExecutor 的后台线程检查引用是否被清除,如果没有,调用GC。
  3. 如果引用还是未被清除,把 heap 内存 dump 到 APP 对应的文件系统中的一个 .hprof 文件中。
  4. 在另外一个进程中的 HeapAnalyzerService 有一个 HeapAnalyzer 使用HAHA 解析这个文件。
  5. 得益于唯一的 reference key, HeapAnalyzer 找到 KeyedWeakReference,定位内存泄露。
  6. HeapAnalyzer 计算 到 GC roots 的最短强引用路径,并确定是否是泄露。如果是的话,建立导致泄露的引用链。
  7. 内存泄漏信息送回给 DisplayLeakService,它是运行在 app 进程里的一个服务。然后通过一个独立的页面显示出来

 

7.在常见的Crash疑难排行榜上,OOM为什么名列前茅且经久不衰?

 

9. UI卡顿的原因有哪些?

 

11.UI结构中fragment缓存原理?

 

12.MeasureSpec 的原理?

 

13.自定义View的measure时机是什么,为什么参数值时而是0,时而正确?

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值