内存优化,不仅可以减少OOM,还可以减少GC的频率,GC频率减少后,这样就会使应用使用更加流畅。减少内存占用,可以提高应用在后台运行时的存活率。当然还可以减少异常的发生和减少代码的逻辑隐患。所以进行内存优化是非常有必要的。

在进行内存优化之前,需要先了解对象在内存中的生命周期以及垃圾回收的原理,可以参考Java虚拟机部分知识点,因为内存优化过程中,涉及到了内存泄漏的排查,这个排查过程,就需要了解对象的生命周期和GC的回收过程。所以,看这篇文章前,请务必了解对象的生命周期和GC的原理。

内存优化始终是贯穿在整个开发过程中的,很多项目组开发的流程大部分是先完成功能,在完成功能后,在进行优化,但是,主动的去进行对代码检测并不能完全发现内存泄漏方面的问题,可能要在测试时,通过应用发生OOM或者应用卡顿时,通过工具分析导致OOM或者卡顿的原因来对内存进行优化,大部分的OOM,都是由于内存泄漏导致的的,下面介绍几个分析内存泄漏的工具。

  • Memory Monitor(关于这个工具的使用,可以参考 Android Studio 3.4中Memory Profiler工具使用 )
Heap Size              表示分配给当前进程的堆大小
 Allocated                表示已经分配的内存
 Free                       表示未使用的内存
 %Used                   表示内存的使用率
 #Objects                 表示对象数
 右侧的一个 Cause GC 按钮可以手动的触发一次GC。
 区域B
 详情视图页展示所有数据类型的内存情况,其中第一列为所有数据类型,类型如下:
 ·free :空闲的对象。
 ·data object :数据对象 Java 类类型对象,是最主要的观察对象。
 ·class object : Java 类类型的引用对象。
 ·1 byte array byte[] boolean[]boolean[]):一字节的数组对象。
 ·2 byte array short[] char[]char[]):两字节的数组对象。
 ·4 byte array long[] double[] : 4 字节的数组对象。
 ·8 byte array long[] double[] :8 字节的数组对象。
 ·non Java object :非 Java 对象。
 在每个类型的数据值对应如下:
 ·Count :数量。
 ·Total Size :总共占用的内存大小 。
 ·Smallest :将对象占用内存从小到大排列,排在第一个的对象占用内存大小。
 ·Largest :将对象占用内存的大小从小到大排列,排在最后一个的对象占用的内存大小。
 ·Median :将对象占用内存的大小从小到大排列,排在中间的对象占用的内存大小。
 ·Average :平均值。
  • Heap View
    Heap View 的主要功能是查看不同数据类型在内存中的使用情况,可以看到当前进程中的 Heap Size 的情况,分别有哪些类型的数据,以及各种类型数据占比情况。通过分析这些数据来找到大的内存对象,再进一步分析这些大对象,进而通过优化减少内存开销,也可以通过数据的变化发现内存泄漏。(注意:Heap View 目前只能由 5.0 及以上系统支持,并且需要开发者模式。)
    Heap View使用:
    1.在Android\sdk\tools目录下,双击 monitor.bat 文件,即可打开Android Device Monitor打开后的界面如下:
  • lua内存优化 内存优化有用吗_内存泄漏

  • 第一步:先选中DDMS,也就是上图中的数字1出的按钮,这样左侧就会出现Devices的信息,如果没看到设备的信息,则可能是设备未连接到AS。
    第二步:选择图中的2处的进程(当然也可以选择其他的进程,这里只是以数字2处的进程为例)
    第三步:点击图中数字3处的"Update Heap"按钮
    第四步:在右侧,选择Heap 这个tab,就可以看到相关的内存信息了
    在上图中,内存的信息总共分为3个部分
    区域A,显示的是当前选中的进程的总体内存情况

  • 上图中的区域C
    选择一个具体数据类型后,会显示对应的内存对象柱状图,柱状图横坐标是对象的内存大小,
    这些值随着不同对象而不同,纵坐标是在某个内存大小上的对象的数量。
  • Allocation Tracker
    Memory Monitor 和 Heap Viewer 都可以很直观且实时地监控内存使用情况,还能发现内存问题,但发现内存问题后不能再进一步找到原因,或者发现一块异常内存,但不能区别是否正常,同时在发现问题后,也不能定位到具体的类或方法。这时就需要使用另一个内存分析工具 Allocation Tracker 进行更详细的分析, Allocation Tracker 可以分配跟踪记录应用程序的内存分配,并列出了它们的 调用堆栈,可以查看所有对象内存分配的周期。
    Allocation Tracker 的主要功能如下:
     ·在一段时间内以对象类型为纬度,跟踪在 此时间内的内存分配和释放情况。
     ·寻找代码中内存使用不合理的地方。
    Allocation Tracker 是分析较短一段时间内的内存使用情况,在使用Allocation Tracker 前,可以先用 Memory Monitor 或者 Heap Viewer 找到内存异常的场景,然后使用 Allocation Tracker 分析这个场景的内存使用情况。
    关于Allocation Tracker的使用,请参考 Android Studio 3.4中Memory Profiler工具使用 这篇文章。Allocation Tracker可以非常方便快捷地得到程序在某一段时间内内存的分配情况,并且能跟踪到分配该内存的具体代码。但是它不能提供任何程序 Heap 的总体情况和 Heap 具体信息。而分析 Heap 的信息,能够查找出更隐蔽的内存泄漏问题,需要使用MAT。
  • Memory Analyzer Tool
    MAT 是一个快速、功能丰富的 Java Heap 分析工具,通过分析 Java进程的内存快照 HPROF 文件,从众多的对象中分析,快速计算出在 内存中对象的占用大小,查看哪些对象不能被垃圾收集器回收,并可以通过视图直观地查看可能造成这种结果的对象。
    在介绍MAT工具的使用之前,先准备一个内存泄漏的示例:
package test.cn.example.com.androidskill.leak;

import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;

import test.cn.example.com.androidskill.R;
import test.cn.example.com.util.LogUtil;

public class InnerClassLeakActivity extends AppCompatActivity {
   private static Leak sLeak;
   @Override
   protected void onCreate(@Nullable Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_leak_common);
       sLeak = new Leak();
       sLeak.test();

   }

   private class Leak {
       private void test(){
           LogUtil.i("test method execute   "+InnerClassLeakActivity.this);
       }
   }
}

(1)下载MAT客户端
https://eclipse.org/mat/downloads.php (2)获取hprof文件
在AS中,打开Profile,点击Memory,点击"Dump Heap Memory"按钮,在Profile面板左侧,会,看到一个Heap Dump的栏目,鼠标放到上面,鼠标右键,Export,便可导出一个hprof文件。需要将 HPROF 文件从 Android 格式转换为 Java SE HPROF 格式。可以使用 android_sdk/platform-tools/ 目录中提供的 hprof-conv 工具执行此操作。
运行包含两个参数(即原始 HPROF 文件和转换后 HPROF 文件的写入位置)的 hprof-conv 命令。例如:

hprof-conv   heap-original.hprof     heap-converted.hprof

(3)对hprof文件进行分析

打开下载好的MAT工具后,将转换后的hprof文件使用MAT工具加载(会弹出一个对话框,可以直接关闭)如下图:

lua内存优化 内存优化有用吗_lua内存优化_02


点击上图的finish按钮后,会看到如下界面(overview界面):

lua内存优化 内存优化有用吗_Heap View_03


在MAT 窗口上, OverView 是一个总体概览,显示总体的内存消耗情况和疑似问题。 MAT 提供了多种分析维度,其中 Histogram 、 Dominator Tree 、 Top Consumers 和 Leak Suspects 的分析纬度不同

MAT 提供的四种 Action

(1) Histogram

列出内存中的所有实例类型对象、对象的个数以及大小,并支持正则表达式查找。

(2) Dominator Tree

列出最大的对象及其依赖存活的Object 。分析流程和 Histogram 大同小异,但 Dominator Tree能更方便地看出引用关系。

(3) Top Consumers

通过图形列出最大的Object 。

(4)Leak Suspects

通过MAT 自动分析泄漏的原因和泄漏的一份总体报告。 Leak Suspects 列出了工具怀疑的内存泄漏点,以及泄漏的内存大小,在后面有问题列表和所有对象,单击对应的 可以看到更深入的分析情况,如下图

lua内存优化 内存优化有用吗_Memory Analysis Tool_04


点击图中红框中的"Details"可以查看更加深入的分析,一般都在Histogram 或者 Dominator Tree 视图中分析内存是否异常。点击overview页面下面下方的Histogram,打开Histogram界面后,在ClassName列名下输入app的包名(部分包名也可以,支持正则表达式),点击回车,就可以看到如下图的结果

lua内存优化 内存优化有用吗_Memory Analysis Tool_05


从上图中,可以看到,这个页面的右侧有三列:

Objects 表示左侧的类的实例对象的个数

Shallow Heap 表示左侧的类的实例对象自身占用的内存的大小(不包括引用的其他对象的大小),单位是byte。 非数组的常规对象,Shallow Heap Size由其成员变量的数量和类型决定的。数组的Shallow Heap Size

由数组元素的类型(对象类型,基本类型)和数组的长度决定。

Retained Heap 表示的是左侧的类的实例对象占用的内存的大小(中文意思是保留堆,它的大小为对象本身大小(即shallow heap大小)与其所引用对象大小之和),换个说法就是当前对象被GC后,从Heap上总共能释放掉的内存,强调是GC后能释放的。即要排除被GC Roots直接或间接引用的对象。(关于Retained Heap的理解,可以参考 这篇文章)

重上图,无法看出是哪个实例对象存在内存泄漏。下面在点击overview界面的Dominator Tree这个选项在ClassName列名下输入包名进行过滤,可以看到如下界面:

lua内存优化 内存优化有用吗_Memory Analysis Tool_06


可以看到test.cn.example.com.androidskill.leak.InnerClassLeakActivity这个类,这个类,按照测试的操作流程,是进入了这个test.cn.example.com.androidskill.leak.InnerClassLeakActivity页面后,在退出返回到上一个页面,按照正常的流程,这个页面应该是被回收的,怎么还会出现在这个界面中呢?怎么它未被回收呢?下面看看这个类是被谁持有,导致内存泄漏的。鼠标选中test.cn.example.com.androidskill.leak.InnerClassLeakActivity这个类,右键"Merge Shortest Paths to GC Roots",继续选择"exclude all phantom/weak/soft etc. references",具体操作如下图所示:

lua内存优化 内存优化有用吗_Allocation Tracker_07


点击上图中的"exclude all phantom/weak/soft etc. references",会看到如下页面:

lua内存优化 内存优化有用吗_Allocation Tracker_08


从上图中可以看到,这个test.cn.example.com.androidskill.leak.InnerClassLeakActivity这个类是被它的一个

成员变量sLeak对象引用了,看到这里,就可以结合代码,定位到内存泄漏的具体原因了。正式由于

sLeak这个静态变量持有了test.cn.example.com.androidskill.leak.InnerClassLeakActivity这个类的对象的引用,导致test.cn.example.com.androidskill.leak.InnerClassLeakActivity退出后,依然无法被GC回收。

  • Object Query Language
    使用完Histogram和Dominator Tree后,在看看OQL使用,OQL(Object Query Language),在MAT中支持对象查询语句,这是一个类似于SQL语句的查询语言,能够用来查询当前内存中满足指定条件的所有的对象。OQL将内存中的每一个不同的类当做一个表,类的对象则是这表中的一条记录,每个对象的成员变量则对应着表中的一列。
    OQL的基本语法和SQL相似,语句的基本组成如下
SELECT * FROM [ INSTANCEOF ] <class name="name"> [ WHERE <filter-expression> ] </filter-expression></class>

所以,输入

select * from instanceof android.app.Activity

会将当前内存中所有Activity及其之类都显示出来,OQL的其他用法,可以参考这篇文章,通过输入上面的查询语句后,点击回车,得到如下图所示的结果:

lua内存优化 内存优化有用吗_内存泄漏_09


从上图中,可以看到,下面的红框中出现了test.cn.example.com.androidskill.leak.InnerClassLeakActivity这个类,看到这个类,结合操作的测试步骤,可以知道这个类是不应该存在的,是应该被回收的,结果是这个类还存在内存中,说明这个类发生了内存泄漏,具体排查是哪里发内存泄漏,排查的方法前面已经介绍过了。介绍完OQL后,下面继续看看,通过另外一种方式来查找内存泄漏:

还是按照前面介绍的收集hprof文件的方式,在收集一个hprof文件,使用mat工具将这个hprof文件也加载,在第二个加载的hprof文件的窗口中,先点击"histogram",然后在窗口下面的,“navigation history"中,选中"histogram”,右键会弹出如图下所示:

lua内存优化 内存优化有用吗_lua内存优化_10


在两个hprof文件中,都做同样的点击"Add to Compare Basket",会看到如下图所示:

lua内存优化 内存优化有用吗_Memory Analysis Tool_11


点击上图中的右侧的红框中的“红色感叹号”按钮,可看到如下图所示的界面:

lua内存优化 内存优化有用吗_内存泄漏_12


从上图中,可以看到两个文件的histogram中的objects和Shallow Heap的数据,可以在这个界面,对两个文件的数据进行对比来分析是否发生内存泄漏的问题。不仅histogram中的各项数据能够进行对比,Dominator Tree中的各项数据也能够对比。