文集目录
ps:喜欢的点赞哦 android性能跟踪分析工具系列 - 目录
好了上次说了 LeakCanary 检测内存泄露,我在最后说了下可以自己抓内存快照来分析内存泄露的,比 LeakCanary 快速多了。好了,不卖关子了,这个就是 studio 自带的工具 Memory monitor,我们来看一下:
这个就是studio 内置的内存监控工具,其实下面还有 CPU/GPU/NetWork 监控工具,这里不说这几个,直说内存监控工具,他主要给我们提供了2个分析功能,分别是我标记的1和2:
- 1 : jump java heap ,抓取堆内存快照,也是本篇介绍的部分
- 2: allocation tracker ,分析内存分配,是一个过程分析工具,从时间A到时间 B 的内存分配,这部分内容之后再说。
然后我们再看图中的图标,这个很简单,但是也很有用的,深蓝色的 app 已经使用的内存值,浅蓝色的已经分配给 JVM但是还未使用的内存值,根据观察,空闲内存不足20%时必定会触发一次 GC,而且大家记不记得4大组件都有一个低内存回调函数:
@Override
public void onLowMemory() {
super.onLowMemory();
}
这个函数我看很多人说都是在空闲内存不足20%时触发的。
好了我们来正式看下本篇的主角 jump java heap
如何使用 jump java heap
要是用 jump java heap 功能很简单,点击上面1的位置的按钮就行,注意啊,这个是抓取堆内存的快照,所以这是分析一个时间节点,而不是时间段哦,不要混了。
第一次看数据量有些大,不要头大,这个还是很好看的:
- 1 : 即使左边的写着 name 的列表,里面有 bety[]/ArrayList[]/String[] 等,这很好理解,就是把当前堆内存中的对象按照分类给我们列出来,比如 ArrayList[] 就是当前内存中的集合对象啦,bety[] 就是字节数组啊,结合android,大家想内存中谁会大量使用bety[]字节数组啊,当然是 bitmap 位图啦,当然我们自定义的对象也是可以看到的,只不过默认的分类排序是按照占用内存大小来排列的。
- 2: 我们点击1中的每个分类,在2中就会把该分类的对象列出来
- 3: 引用指向这块内查的对象有哪些
- 4: 这是内存泄露的分析工具,一会说
好了,整个工具我们粗粗的看了下, 先有个简单的认知,大家仔细看可以看到每一块都会显示很多参数数据对吧,这个其实才是重中之重
数据参数详解:
- total :是一共有多少个对象
- heap count:是一共分配了多少快内存空间,我看这个参数没啥用。
- size of:是平均一个对象的大小
- Shallow Size: 是这些对象一共占用了多少内存
- Retained Sizes/Domintaing Sizes: 这些对象释放以后可以获得多少内存,这个值最有用,和实际的内存变化值对的上。
- depth : 对象引用层级,从 GC root 根节点开始算。
我们点击每一个参数都是可以排序的哦
结合实际例子学分析
我准备了一个小测试,可以学习如何查看我们想要找的对象,也可以查找 activity 和 非 activity类型对象的内存泄露,这俩其实是一回事,因为都是可以在这张对内存快照中看出来的
例子: A 页面点 按钮启动 B 页面,在B 页面创建几个数据,一个 Book 对象,一个 Book 对象的集合,然后把这个 Book 对象和 Book 对象的集合放到 application 的静态对象中,模拟内存泄露
public class BActivity extends AppCompatActivity {
private ArrayList<Book> mList;
private Book mBook;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_b);
mBook = new Book("HHH");
mList = new ArrayList<>(10000);
MyApplication.getInstance().mActivity = this;
MyApplication.getInstance().mList = mList;
MyApplication.getInstance().mBook = mBook;
}
代码,大家看一下,很简单,我们这么玩,A 启动 B 后,我们看下此时 B 的堆内存快照,然后我们关闭 B页面再来看一下堆内存快照
A 启动 B :
首先,我们点击左上角的选择款,选择按包分类,选择上图蓝色的标签
按包排序后,我们再来找我们自定义的对象就方便啦,我们先看 book 这个对象,book 对象的 total 是1,说明此时堆内存只有1个 book 对象,然后右边的 instance 也是只有一个,说明book 对象的确是只有一个,然后我们看下面有2个 mBook对象,这说明有2个对象的引用指向堆内存中的这个 book 对象,工具很细心的给我们列出了引用关系,一个在 application,一个在 B 页面。
我们再来看下 BActivity 这个对象,右面的 instance点击是可以展开的,展开后我们可以看到 B 页面对象内的所有参数,上图我们按实际内存消耗大小排序的,可以看到我们声明的那个10000个容量的mlist集合对象占用40K 内存,消耗很大的,所以我们平时写 java 代码有其是 new 对象时一定要谨慎,内存就是这么消耗没的,哈哈至于 mbook 对象,因为内存消耗小自然在下面,大家翻翻都能找到。
恩,这样我们就可以看到一个页面的总消耗和哪里消耗最大了,这样我们是可以找到优化点的
关闭 B 返回 A:
关闭 B 页面后,我们看下此时app内存占用,还是5.8M,和 B 显示时一样,这说明啥,肯定是内存泄露了呗,B 对象没被回收啊
我们再来看下内存快照,这时 BActiviy的 total 还是1,说明 b 页面对象还在,这就是妥妥的泄露了,从这2点都能看出来,尤其是你看下面,蓝色的那一条,出现蓝色就说明这里泄露了,引用关系指向 application,这下大家会找了吧,这个不难吧。
我们再来看 book 对象,book 对象里也有一条蓝色,说明 book 对象也泄露了,当然下面的那个引用藏在 B 页面对象里了,B 对象收回了,这个 book 对象也跟着回收了,不是直接泄露,这个工具是检测不出来的。
肯定有人就问了,appcalition 里不是还有 book 的集合不是也泄露了嘛,是的,集合类型不是我们自定义的,是在 java 包下面的,我们需要在 java 包下面去找。
这里有一个小技巧要说,很重要,不注意可能找不到结果的,在右边对象列表中,尤其是像系统集合这类的,里面的对象很多,默认不会都显示出来,我们需要拉倒最底下,点击展开全部,这点很重要,不要漏掉
然后我们按照 实际消耗内存排序,可以看到第一个就是我们要找的,看下面也是大蓝条,一样是内存泄露的,引用也是指向 application 的。
另外这个工具可以自动分析 activity 对象的内存泄露,我们点击右边的 Analyzer tasks 展开,然后点击左上角的作色箭头就好了。
这个工具只能分析 activity 对象,要是能分析全部就好了。 哈哈,说到这里基本这个工具如何使用就介绍完了,对于查找内存泄露是不是比 LeakCanary 快多了,就是比他麻烦些,需要我们自己去翻
不过呢,这个工具最麻烦的一点就在于需要我们在可能泄露的点不停的去点一下,看看没有没蓝条。需要一些经验才能真正达到快速高效,所以说有一个好鼠标很重要哇,哈哈哈。
如何查看图片的内存消耗
写完上面呢基本的这个内存快照打击就知道怎么看了,但是呢这里我还是要重点说说 bitmap 位图的内存消耗,一个 app 内存消耗的主力可是bitmap,这块我们必须要会看。
bitmap 位图在内存快照中快照中的位置我在上面说了,是 byte[] ,字节数组中
例子:还是结合一个例子来说,一个页面,初始不加载图片,有一个按钮,点一下加载一张大图,1080P的模拟一下,在点击的同事回收上一次显示的 bitmap 内存,回收和回收我们都来看一下。
切换图片
public void nextImage(View view) {
List<Integer> images = getImages();
if (index == images.size() - 1) {
index = 0;
}
recycleBitmap(image);
image.setImageResource(images.get(index));
index++;
}
回收 bitmap 资源
public void recycleBitmap(View view) {
Toast.makeText(this, "test", Toast.LENGTH_SHORT).show();
Drawable drawable = image.getDrawable();
if (drawable != null && drawable instanceof BitmapDrawable) {
BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
Bitmap bitmap = bitmapDrawable.getBitmap();
if (bitmap != null && !bitmap.isRecycled()) {
bitmap.recycle();
}
}
}
代码很简单,我们分阶段看下,显示1张图,2张图,3张图,对比下
不回收 bitmap 资源
app初始:
显示一张图片:
显示二张图片:
显示三张图片:
具体我就不算了,大家可以看到随着图片的切换显示,内存使用量是在不停攀升的,内存上涨的幅度和 byte[] 数组增长的幅度一致,我们直接看最后一张图,这是显示3张图片之后,可以看到内存占用前4个都是 bitmap 占用的,这时我们使用工具上的手动 GC 看看,能回收到少内存。
手动GC之后,可以看到内存下来了,byte[]组数中只有前2个是bitmap 占用的了,之前的2个打 bitmap 被回收了,这说明bitmap 回收异常重要,大家看看内存消耗都能深有体会了吧。
那么我们来看看我们主动回收bitmap 资源会怎样把,是不是和我们想的一样
回收 bitmap 资源
这次简单点,不上这么多图了,代码上添加了回收资源的方法 : recycleBitmap(image);
显示1张图:
显示2张图:
显示3张图:
可以看到随着我们不停显示图片,app内存消耗还是在不停上涨的,我在看下 byte[] 中强5个都是 bitmap 的占用,然后我们点击第一个bitmap 对象
可以看到,bitmap.recycler 只是把bitmap对象的引用清理掉了,然后等着 GC 回收了,但是这几次并没有触发 GC,然后继续切换图片,显示到第5张时触发了 GC
可以看到 GC 的确回收了 没有引用关系bitmap 的内存,但是还有一个没有引用关系的bitmap没有被回收,看来我们把 bitmap.recycler 之后,马上 GC 的话,这个 bitmap 会被忽略,至于为啥,我也不知道啊,也许是IPC 延迟的问题吧。
可以看到 GC 的对于 bitmap 的重要性了,既然系统 GC 相应的不及时,那么我们就自动手动调用 GC 好了,当然 GC 不要很频繁,因为 GC 操作是会卡 UI线程的,我觉得在 application 的全局生命周期监听函数中,在 activity 的销毁那里调一下可能是个不错的时机
另外 5.0 之后 使用 System.gc 不管用了,需要这样才行 Runtime.getRuntime().gc();
this.registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
@Override
public void onActivityDestroyed(Activity activity) {
Runtime.getRuntime().gc();
}
ps:关于bitmap 这块都是我不成熟的认识,有错误请留言,有好的方案也屏留言。
AS 3.0 性能分析工具的新变化
AS 升级 3.0 之后,性能分析工具变化较大,首先性能分析工具被分到了一个新的位置 Android Prefiler 中,这个功能窗口默认是加入底部的,需要自己开启
新的 Android Prefiler 功能窗口主要变化在于5个部分:
- 设备
- 当前选择的应用进程
- 时间线缩放操作
- 跳转到详情界面,点击时间轴也可以
- 高级信息显示
可以显示页面活动状态,用户输入事件和屏幕旋转事件
默认是不显示 5 这部分内容的,因为会增加构建,编译时间,此时会在5的位置会提示
Advanced profiling is unavailable for the selected process
高级数据显示包括以下内容:
- 所有分析器窗口上的事件时间轴
- activity 页面显示和切换
- 内存分析器中已分配对象的数量
- 内存分析器中的垃圾收集事件
- 有关Network Profiler中所有传输文件的详细信息
如何启用高级数据显示:
- 单击自动提示的 Edit Configurations 或是 Run > Edit Configurations
- 在左窗格中选择您的应用程序模块。
- 单击Profiling选项卡,然后选中Enable advanced profiling.
- 必须重新构建安装才可以