Android篇

性能优化(二):内存优化基础知识

2021-03-31  本文已影响0人  w达不溜w
1.Java的对象生命周期
  1. 创建(Create
    为对象分配存储空间
    开始构造对象
    从超类到子类对static成员进行初始化
    超类成员变量按顺序初始化,递归调用超类的构造函数
    子类成员变量按顺序初始化,子类构造方法调用

  2. 应用(In use)
    对象至少被一个强引用持有。

  3. 不可见(Invisible)
    当一个对象处于不可见阶段,说明程序本身不再持有该对象的任何引用,但是这些引用可能还存在着。一般具体指程序的执行已经超过该对象的作用域了。

  4. 不可达(Unreachable)
    该对象不再被任何强引用所持有。

  5. 收集(Collected)
    finalize()方法的调用时机。

  6. 终结(Finalized)
    对象运行完finalize()方法后仍处于不可达状态,则进入终结阶段,等待垃圾回收器对该对象空间进行回收。

  7. 对象空间重新分配(Deallocated)
    垃圾回收器对该对象所占用的内存空间进行回收或者再分配,则该对象彻底消失。

2.Android内存分配与回收机制
Android内存回收机制.jpg

新生代区域分为eden区、S0和S1区,S1和S2(合称Survivor区)是两块大小相等并且可以互换角色的空间。(1)对象创建后在eden区;(2)执行GC后,如果对象还存活会进入S0或S1区;(3)每一个GC,存活的对象年龄会相应增加,达到一定年龄会进入老年代;(4)最后累积一定时间再移动到持久代区域。

3.Java的四种引用
4.垃圾回收算法
  1. 标记清除算法
    标记清除算法分为两个阶段:标记阶段和清除阶段。标记阶段的任务是标记出所有需要被回收的对象,清除阶段就是回收被标记的对象所占用的空间。

    • 位置不连续,产生碎片
    • 效率略低
    • 两遍扫描
      标记清除算法.jpg
  2. 复制算法
    将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就是将还存活的对象复制到另一块上面,然后再把已使用的内存空间一次性清理掉。

    • 实现简单、运行高效
    • 没有内存碎片
    • 利用率只有一半
      复制算法.jpg
  3. 标记整理算法
    完成标记后,不是直接清理可回收对象,而是将存活对象都向一段移动,然后清理掉端边界以外的内存。

    • 没有内存碎片
    • 效率偏低
    • 两遍扫描、指针需要调整
      标记整理算法.jpg

分代收集算法
核心思想是根据对象存活的生命周期将内存划分为若干不同区域。

新生代:复制算法(因为每次垃圾回收都要回收大部分对象,需要复制操作少)

老年代:标记整理算法(每次回收只回收少量对象)

5.App内存组成以及限制

Android给每个App分配一个VM,让App运行在Dalvik上,这样即使App崩溃也不会影响到系统。系统给VM分配了一定的内存大小,App可以申请使用的内存大小不能超过此硬性逻辑限制,就算物理内存富余,如果应用超过VM最大内存,就会出现内存溢出crash。

由程序控制操作的内存空间在heap上,分为java heapsize和native heapsize

6.Android进程优先级

进程的优先级从高到低,它们的oom_adj值也是从小到大:

  1. Foreground process(前台进程)
  2. Visible process(可见进程)
  3. Service process(服务进程)
  4. Background process(后台进程)
  5. Empty process(空进程)
7.内存三大问题
  1. 内存抖动
    短时间内有大量的对象被创建和回收的现象。

  2. 内存泄漏
    本该回收的对象因为某些原因不能回收。

  3. 内存溢出
    OOM,使用内存大于申请内存。

8.Android内存泄漏常见常见及解决方案
  1. 资源对象未关闭
    对于资源对象不再使用时,应该立即调用它的close()将其关闭,然后再置为null。例如Bitmap 等资源未关闭会造成内存泄漏,此时我们应该在Activity销毁时及时关闭。

  2. 注册对象未注销
    BroadcastReceiver、EventBus等未注销导致内存泄漏,我们应该在Activity销毁时及时注销。

  3. 类的静态变量持有大对象
    尽量避免静态变量存储数据,特别是大数据对象,建议使用数据库等存储。

  4. 单例造成的内存泄漏
    优先使用Application的Context,如需使用Activity的Context,可以在传入Context时使用弱引用进行封装,然后在使用到的地方从弱引用中获取Context,如果获取不到直接return即可。

  5. 非静态内部类的静态实例
    静态实例的生命周期和应用一样长,非静态内部类会持有外部类的引用,使得该静态实例一直持有Activity的引用,导致内存泄漏。我们可以将该内部类设为静态内部类。

  6. Handler引起的内存泄漏
    Handler允许我们发送延时消息,如果延时期间用户关闭了Activity,那么该Activity就会泄漏。

    这个泄漏是因为Message会持有handler,而又因为Java的特性,非静态内部类会持有外部类,使得 Activity会被Handler持有,这样最终导致了了Activity泄漏。

    解决该问题的最有效方法是:将Handler定义为静态内部类,在内部类中持有Activity的弱引用,并及时移除所有消息。(被弱引用关联的对象只能存活到下一次垃圾回收之前,被销毁的Activity会被回收内存)

     private static class SafeHandler extends Handler {
        private WeakReference<HandlerActivity> ref;
        public SafeHandler(HandlerActivity activity) {
            this.ref = new WeakReference(activity);
         }
       
         @Override
        public void handleMessage(final Message msg) {
            HandlerActivity activity = ref.get();
            if (activity != null) {
               activity.handleMessage(msg);
            } 
        }
    }
    

    并且再在Activity.onDestory()前移除消息,加一层保障

    @Override
    protected void onDestroy() {
      safeHandler.removeCallbacksAndMessages(null);
      super.onDestroy();
    }
    
  7. 容器中的对象没清理造成的内存泄漏
    退出程序之前,将集合里的东西clear并置为null。

  8. WebView内存泄漏
    WebView都存在内存泄漏的问题,在应用中只要使用一次WebView,内存就不会被释放掉。我们可以为 WebView开启一个独立的进程,使用AIDL与应用的主进程进行通信,WebView所在的进程可以根据业务的需要选择合适的时机进行销毁,达到正常释放内存的目的。

上一篇下一篇

猜你喜欢

热点阅读