性能优化--内存优化

133 阅读15分钟

Java虚拟机

内存分配

Java程序在运行的过程中会将其管理的内存分为若干个不同的数据区:

d38ee9ccbde20d9dec8f139a347bd30a~tplv-t2oaga2asx-jj-mark_3024_0_0_0_q75.png

  • 方法区:方法区存放的是类信息、常量、静态变量,所有线程共享区域。

  • 虚拟机栈:每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息,线程私有区域。

  • 本地方法栈:与虚拟机栈类似,区别是虚拟机栈为虚拟机执行Java方法服务,本地方法栈为虚拟机使用到的Native方法服务

  • JVM管理的内存中最大的一块,所有线程共享;用来存放对象实例,几乎所有的对象实例都在堆上分配内存;此区域也是垃圾回收器(Garbage Collection)主要的作用区域,内存泄漏就发生在这个区域

  • 程序计数器可看做是当前线程所执行的字节码的行号指示器;如果线程在执行Java方法,这个计数器记录的是正在执行的虚拟机字节码指令地址;如果执行的是Native方法,这个计数器的值为空(Undefined)。

内存回收

1标记-清除算法

分为“标记”和“清除”两个阶段,首先,标记出所有需要回收的对象,然后统一回收所有被标记的对象。

7964619ffb03b1555806a945fa7781bd~tplv-t2oaga2asx-jj-mark_3024_0_0_0_q75.png

这种方法有两个不足点:

  1. 效率问题,标记和清除两个过程的效率都不高;
  2. 空间问题,标记清除之后会产生大量的不连续的内存碎片。

2复制算法

将内存按容量划分为大小相等的两块,每次只使用其中的一块,当这一块内存将用完了,就将还存活着的对象复制到另一块内存上面,然后再把已使用过的内存空间一次清理掉。
这种方法的特点:

af0bb9fd76c8c4a262472dd55ebdcefc~tplv-t2oaga2asx-jj-mark_3024_0_0_0_q75.png

优点:实现简单,运行高效;每次都是对整个半区进行内存回收,内存分配时也不需要考虑内存碎片等情况,只要移动堆顶指针,按顺序分配内存即可;

缺点:粗暴的将内存缩小为原来的一半,代价实在有点高。

3标记-整理算法

先标记需要回收的对象(标记过程与“标记-清除”算法一样),然后把所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。

fdf304dcb8b32af478ee714b9c4ed59b~tplv-t2oaga2asx-jj-mark_3024_0_0_0_q75.png

避免了内存碎片;

避免了“复制”算法50%的空间浪费;

主要针对对象存活率高的老年代。

分代收集算法 根据对象的存活周期的不同将内存划分为几块,一般是把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。在新生代中,每次垃圾收集时都会发现有大量对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用标记—清除算法或标记—整理算法来进行回收。

GC垃圾回收器

GC如何确定内存回收?

1: 引用计数算法

给对象中添加一个引用计数器,每当有一个地方引用该对象时,计数器值加1;引用失效时,计数器值减1;任意时刻计数器为0的对象就是不可能再被使用的,表示该对象不存在引用关系。

这种方法的特点

  • 优点:实现简单,判定效率也很高;
  • 缺点:难以解决对象之间相互循环引用导致计数器值不等于0的问题。

2: 可达性分析算法

以一系列成为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连(GC Roots到这个对象不可达),则证明此对象是不可用的。

8483e14aad74e69d769a11c9499ce361~tplv-t2oaga2asx-jj-mark_3024_0_0_0_q75.png

回收也和引用类型有关系

强引用 Object obj=new Object();

软引用SoftReference 内存不足时回收,存放一些重要性不是很强又不能随便让清除的对象,比如图片切换到后台不需要马上显示了

弱引用WeakReference 第一次扫到了,就标记下来,第二次扫到直接回收

虚引用PhantomReference 幽灵 幻影引用 不对生存造成任何影响,用于跟踪GC的回收通知

Android虚拟机

Android系统的ART和Dalvik虚拟机扮演了常规的内存垃圾自动回收的角色App唯一释放内存的方法就是释放App持有的对象引用,使GC可以回收。

在Dalvik下,大部分Davik采取的都是标记-清理回收算法,而且具体使用什么算法是在编译期决定的,无法在运行的时候动态更换。

ART在GC上不像Dalvik仅有一种回收算法,ART在不同的情况下会选择不同的回收算法。应用程序在前台运行时,响应性是最重要的,因此也要求执行的GC是高效的。相反,应用程序在后台运行时,响应性不是最重要的,这时候就适合用来解决堆的内存碎片问题。

常见内存泄漏

1:集合类

集合类 添加元素后,仍引用着 集合元素对象,导致该集合元素对象不可被回收,从而 导致内存泄漏

// 通过 循环申请Object 对象 & 将申请的对象逐个放入到集合List
List<Object> objectList = new ArrayList<>();        
       for (int i = 0; i < 10; i++) {
            Object o = new Object();
            objectList.add(o);
            o = null;
        }
// 虽释放了集合元素引用的本身:o=null)
// 但集合List 仍然引用该对象,故垃圾回收器GC 依然不可回收该对象

解决方案: 集合类添加集合元素对象后,在使用后必须从集合中删除。由于一个集合中有许多元素,故最简单的方法是:清空集合对象和设置为null。

 // 释放objectList
        objectList.clear();
        objectList=null;

2:Static 关键字修饰的成员变量

被 Static 关键字修饰的成员变量的生命周期 = 应用程序的生命周期

若使被 Static 关键字修饰的成员变量 引用耗费资源过多的实例(如Context),则容易出现该成员变量的生命周期 > 引用实例生命周期的情况,当引用实例需结束生命周期销毁时,会因静态变量的持有而无法被回收,从而出现内存泄露

public class ClassName {
 // 定义1个静态变量
 private static Context mContext;
 //...
// 引用的是Activity的context
 mContext = context; 

// 当Activity需销毁时,由于mContext = 静态 & 生命周期 = 应用程序的生命周期,故 Activity无法被回收,从而出现内存泄露

}

解决方案:

  1. 尽量避免 Static 成员变量引用资源耗费过多的实例(如 Context),若需引用 Context,则尽量使用ApplicaitonContext

  2. 使用 弱引用(WeakReference) 代替 强引用 持有实例

3:静态成员变量有个非常典型的例子 = 单例模式

单例模式 由于其静态特性,其生命周期的长度 = 应用程序的生命周期

若1个对象已不需再使用 而单例对象还持有该对象的引用,那么该对象将不能被正常回收 从而 导致内存泄漏

// 创建单例时,需传入一个Context
// 若传入的是Activity的Context,此时单例 则持有该Activity的引用
// 由于单例一直持有该Activity的引用(直到整个应用生命周期结束),即使该Activity退出,该Activity的内存也不会被回收
// 特别是一些庞大的Activity,此处非常容易导致OOM

public class SingleInstanceClass {    
    private static SingleInstanceClass instance;    
    private Context mContext;    
    private SingleInstanceClass(Context context) {        
        this.mContext = context; // 传递的是Activity的context
    }  
  
    public SingleInstanceClass getInstance(Context context) {        
        if (instance == null) {
            instance = new SingleInstanceClass(context);
        }        
        return instance;
    }
}

解决方案: 如上述实例,应传递ApplicationContext,因Application的生命周期 = 整个应用的生命周期


public class SingleInstanceClass {    
    private static SingleInstanceClass instance;    
    private Context mContext;    
    private SingleInstanceClass(Context context) {        
        this.mContext = context.getApplicationContext(); // 传递的是Application 的context
    }    

    public SingleInstanceClass getInstance(Context context) {        
        if (instance == null) {
            instance = new SingleInstanceClass(context);
        }        
        return instance;
    }
}

4 非静态内部类 / 匿名类

非静态内部类 / 匿名类 默认持有 外部类的引用;而静态内部类则不会

常见分3种,分别是:非静态内部类的实例 = 静态、多线程、消息传递机制(Handler

  • 4.1 非静态内部类的实例 = 静态

若 非静态内部类所创建的实例 = 静态(其生命周期 = 应用的生命周期),会因 非静态内部类默认持有外部类的引用 而导致外部类无法释放,最终 造成内存泄露

例如:

// 背景:
// a. 在启动频繁的Activity中,为了避免重复创建相同的数据资源,会在Activity内部创建一个非静态内部类的单例
// b. 每次启动Activity时都会使用该单例的数据

public class TestActivity extends AppCompatActivity {  
    
    // 非静态内部类的实例的引用
    // 注:设置为静态  
    public static InnerClass innerClass = null; 
   
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {        
        super.onCreate(savedInstanceState);   

        // 保证非静态内部类的实例只有1个
        if (innerClass == null)
            innerClass = new InnerClass();
    }

    // 非静态内部类的定义    
    private class InnerClass {        
        //...
    }
}

// 造成内存泄露的原因:
// a. 当TestActivity销毁时,因非静态内部类单例的引用(innerClass)的生命周期 = 应用App的生命周期、持有外部类TestActivity的引用
// b. 故 TestActivity无法被GC回收,从而导致内存泄漏

解决方案:

1:将非静态内部类设置为:静态内部类(静态内部类默认不持有外部类的引用)

2: 该内部类抽取出来封装成一个单例

3: 尽量 避免 非静态内部类所创建的实例 = 静态

  • 4.2 多线程:AsyncTask、实现Runnable接口、继承Thread类

多线程的使用方法 = 非静态内部类 / 匿名类;即 线程类 属于 非静态内部类 / 匿名类

泄漏原因: 当工作线程正在处理任务而外部类需销毁时, 由于 工作线程实例 持有外部类引用,将使得外部类无法被垃圾回收器(GC)回收,从而造成 内存泄露


  /** 
   * 方式1:新建Thread子类(内部类)
   */  
    public class MainActivity extends AppCompatActivity {

    public static final String TAG = "carson:";
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 通过创建的内部类 实现多线程
        new MyThread().start();

    }
    // 自定义的Thread子类
    private class MyThread extends Thread{
        @Override
        public void run() {
            try {
                Thread.sleep(5000);
                Log.d(TAG, "执行了多线程");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

 /** 
   * 方式2:匿名Thread内部类
   */ 
    public class MainActivity extends AppCompatActivity {

    public static final String TAG = "carson:";

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 通过匿名内部类 实现多线程
        new Thread() {
            @Override
            public void run() {
                try {
                    Thread.sleep(5000);
                    Log.d(TAG, "执行了多线程");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        }.start();
    }
}


/** 
* 分析:内存泄露原因
*/ 
// 工作线程Thread类属于非静态内部类 / 匿名内部类,运行时默认持有外部类的引用
// 当工作线程运行时,若外部类MainActivity需销毁
// 由于此时工作线程类实例持有外部类的引用,将使得外部类无法被垃圾回收器(GC)回收,从而造成 内存泄露

解决方案:

/** 
 * 解决方式1:静态内部类
 * 原理:静态内部类 不默认持有外部类的引用,从而使得 “工作线程实例 持有 外部类引用” 的引用关系 不复存在
 * 具体实现:将Thread的子类设置成 静态内部类
 */  
  public class MainActivity extends AppCompatActivity {

  public static final String TAG = "carson:";
  @Override
  public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);

      // 通过创建的内部类 实现多线程
      new MyThread().start();

  }
  // 分析1:自定义Thread子类
  // 设置为:静态内部类
  private static class MyThread extends Thread{
      @Override
      public void run() {
          try {
              Thread.sleep(5000);
              Log.d(TAG, "执行了多线程");
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
      }
  }
}

/** 
 * 解决方案2:当外部类结束生命周期时,强制结束线程
 * 原理:使得 工作线程实例的生命周期 与 外部类的生命周期 同步
 * 具体实现:当 外部类(此处以Activity为例) 结束生命周期时(此时系统会调用onDestroy()),强制结束线程(调用stop())
 */ 
 @Override
  protected void onDestroy() {
      super.onDestroy();
      Thread.stop();
      // 外部类Activity生命周期结束时,强制结束线程
  }
  • 4.3 消息传递机制:Handler

由于Handler = 非静态内部类 / 匿名内部类(2种使用方式),故又默认持有外部类的引用

   /** 
     * 方式1:新建Handler子类(内部类)
     */  
    public class MainActivity extends AppCompatActivity {

            public static final String TAG = "carson:";
            private Handler showhandler;

            // 主线程创建时便自动创建Looper & 对应的MessageQueue
            // 之后执行Loop()进入消息循环
            @Override
            protected void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                setContentView(R.layout.activity_main);

                //1. 实例化自定义的Handler类对象->>分析1
                //注:此处并无指定Looper,故自动绑定当前线程(主线程)的Looper、MessageQueue
                showhandler = new FHandler();

                // 2. 启动子线程1
                new Thread() {
                    @Override
                    public void run() {
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        // a. 定义要发送的消息
                        Message msg = Message.obtain();
                        msg.what = 1;// 消息标识
                        msg.obj = "AA";// 消息存放
                        // b. 传入主线程的Handler & 向其MessageQueue发送消息
                        showhandler.sendMessage(msg);
                    }
                }.start();

                // 3. 启动子线程2
                new Thread() {
                    @Override
                    public void run() {
                        try {
                            Thread.sleep(5000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        // a. 定义要发送的消息
                        Message msg = Message.obtain();
                        msg.what = 2;// 消息标识
                        msg.obj = "BB";// 消息存放
                        // b. 传入主线程的Handler & 向其MessageQueue发送消息
                        showhandler.sendMessage(msg);
                    }
                }.start();

            }

            // 分析1:自定义Handler子类
            class FHandler extends Handler {

                // 通过复写handlerMessage() 从而确定更新UI的操作
                @Override
                public void handleMessage(Message msg) {
                    switch (msg.what) {
                        case 1:
                            Log.d(TAG, "收到线程1的消息");
                            break;
                        case 2:
                            Log.d(TAG, " 收到线程2的消息");
                            break;


                    }
                }
            }
        }

   /** 
     * 方式2:匿名Handler内部类
     */ 
     public class MainActivity extends AppCompatActivity {

        public static final String TAG = "carson:";
        private Handler showhandler;

        // 主线程创建时便自动创建Looper & 对应的MessageQueue
        // 之后执行Loop()进入消息循环
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);

            //1. 通过匿名内部类实例化的Handler类对象
            //注:此处并无指定Looper,故自动绑定当前线程(主线程)的Looper、MessageQueue
            showhandler = new  Handler(){
                // 通过复写handlerMessage()从而确定更新UI的操作
                @Override
                public void handleMessage(Message msg) {
                        switch (msg.what) {
                            case 1:
                                Log.d(TAG, "收到线程1的消息");
                                break;
                            case 2:
                                Log.d(TAG, " 收到线程2的消息");
                                break;
                        }
                    }
            };

            // 2. 启动子线程1
            new Thread() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    // a. 定义要发送的消息
                    Message msg = Message.obtain();
                    msg.what = 1;// 消息标识
                    msg.obj = "AA";// 消息存放
                    // b. 传入主线程的Handler & 向其MessageQueue发送消息
                    showhandler.sendMessage(msg);
                }
            }.start();

            // 3. 启动子线程2
            new Thread() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    // a. 定义要发送的消息
                    Message msg = Message.obtain();
                    msg.what = 2;// 消息标识
                    msg.obj = "BB";// 消息存放
                    // b. 传入主线程的Handler & 向其MessageQueue发送消息
                    showhandler.sendMessage(msg);
                }
            }.start();

        }
}

解决方案

  • 1: 设置为静态
public class MainActivity extends AppCompatActivity {

    public static final String TAG = "carson:";
    private Handler showhandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 实例化自定义的Handler类对象->>分析1
        // 注:
            // a. 此处并无指定Looper,故自动绑定当前线程(主线程)的Looper、MessageQueue;
            // b. 定义时需传入持有的Activity实例(弱引用)
        showhandler = new FHandler(this);

        new Thread() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // a. 定义要发送的消息
                Message msg = Message.obtain();
                msg.what = 1;// 消息标识
                msg.obj = "AA";// 消息存放

                showhandler.sendMessage(msg);
            }
        }.start();

    }

    // 设置为:静态内部类
    private static class FHandler extends Handler{

        // 定义 弱引用实例
        private WeakReference<Activity> reference;

        // 在构造方法中传入需持有的Activity实例
        public FHandler(Activity activity) {
            // 使用WeakReference弱引用持有Activity实例
            reference = new WeakReference<Activity>(activity); }

        // 通过复写handlerMessage() 从而确定更新UI的操作
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case 1:
                    Log.d(TAG, "收到线程1的消息");
                    break;
                case 2:
                    Log.d(TAG, " 收到线程2的消息");
                    break;


            }
        }
    }
}
  • 2:当外部类结束生命周期时,清空Handler内消息队列
@Override
    protected void onDestroy() {
        super.onDestroy();
        mHandler.removeCallbacksAndMessages(null);
        // 外部类Activity生命周期结束时,同时清空消息队列 & 结束Handler生命周期
    }

5:资源对象使用后未关闭

对于资源的使用(如 广播BraodcastReceiver、文件流File、数据库游标Cursor、图片资源Bitmap等),若在Activity销毁时无及时关闭 / 注销这些资源,则这些资源将不会被回收,从而造成内存泄漏

解决方案:

// 对于 广播BraodcastReceiver:注销注册
unregisterReceiver()

// 对于 文件流File:关闭流
InputStream / OutputStream.close()

// 对于数据库游标cursor:使用后关闭游标
cursor.close()

// 对于 图片资源Bitmap:Android分配给图片的内存只有8M,若1个Bitmap对象占内存较多,当它不再被使用时,应调用recycle()回收此对象的像素所占用的内存;最后再赋为null 
Bitmap.recycle();
Bitmap = null;

// 对于动画(属性动画)
// 将动画设置成无限循环播放repeatCount = “infinite”后
// 在Activity退出时记得停止动画

6:尽量使用IntentService,而不是Service

内存泄露的工具

  1. MAT(Memory Analysis Tools)
  2. Android Profiler的使用
在图型用户界面上选择要分析的一段内存,右键export出来 
Allocations:  动态分配对象个数
Deallocation:解除分配的对象个数
Total count :对象的总数
Shallow Size:对象本身占用的内存大小
Retained SizeGC回收能收走的内存大小
  1. LeakCanary

参考

www.jianshu.com/p/97fb764f2… juejin.cn/post/684490… juejin.cn/post/684490…