Android内存性能测试

本文涉及的产品
性能测试 PTS,5000VUM额度
简介: Android应用大部分性能问题归根结底都会成为内存的问题,今天我们就先以Out of Memory(OOM)为起点介绍一下Android内存的原理以及排查内存问题的方法。

原理



在讲OOM之前我们先来弄清楚几个概念:内存泄漏、内存抖动、内存溢出


  • 内存泄漏:内存泄漏是指没有用的对象资源仍与GC-Root保持可达路径,导致系统无法进行回收;
  • 内存抖动:内存抖动是因为大量的对象被创建又在短时间内马上被释放,导致频繁 GC;
  • 内存溢出:我们需要一定的内存大小,但是系统无法分配给我们,满足不了我们的需求,所以会导致OOM;


既然我们知道了什么是内存溢出,那它是什么时候发生的呢?答案是在虚拟机的Heap内存使用超过堆内存最大值(Max Memory Heap)的时候,那么在这里大家需要理解的第一个概念就是Dalvik(ART)虚拟机的最大堆内存。


虚拟机的堆内存最大值


在虚拟机中,Android系统给堆(Heap)内存设置了一个最大值,可以通过runtime.getruntime().maxmemory()获取。而因为游戏消耗内存特别大的原因,Android给开通了一个绿色通道,可以在manifest里面设置LargeHeap为true。


虽然我们的手机拥有动辄8GB、16GB的内存,但系统只会分给每个应用一小部分。比如Nexus7单个应用的最大可用内存是192MB,这个值一般在Android设备出厂以后就固定下来了,分这么小内存有一个重要的原因,是Android默认没有虚拟内存。在内存资源稀缺的大背景下,为了保证在极端情况下,前台App和系统还能稳定运行,就只有靠low memory killer机制。

Low Memory Killer


下面引出另一个重要概念Low Memory Killer,也是App消耗内存过大导致的另外一个结果。在手机剩余内存低于内存警戒线的时候,就会召唤Low Memory Killer这个劫富济贫的“杀手”在后台默默干活。这里需要记住一句:App占用内存越多,被Low Memory Killer处理掉的机会就越大。


如果OOM和Low Memory Killer都没有干掉你的App,那也不代表App就没有内存问题,因为还有一类问题,会直接导致App卡顿,那就是GC。


GC


最简单的理解就是没有被GC ROOT间接或直接引用的对象的内存会被回收。在具体执行中,ART和Dalvik会有很多不同,并发GC的时候ART会比Dalvik少一个stop-the-world的阶段,因此Dalvik比ART更容易产生Jank(卡顿),当然,无论ART还是Dalvik并发GC的stop-the-world的时间并不长。然而,糟糕的情况是GC for Alloc,这个情况在内存不足以分配给新的对象时触发,它stop-the-world的时间因为GC无法并发而变得更长。


那么说到底,我们还是要避免GC FOR ALLOC,跟要避免OOM一样,关键是要管理好内存。什么是管理好内存?除了减少内存的申请回收外,更重要的是减少常驻内存和避免内存泄漏,说起内存泄漏,就必须要提Activity内存泄漏。


Activity内存泄漏


因为Activity对象会间接或者直接引用View、Bitmap等,所以一旦无法释放,会占用大量内存,如下图:


微信图片_20220518230445.jpg


图片缓存


另外一个情况就是内存常驻了,而通常在常驻内存中最大的就是图片。现在很多互联网产品APP中都有大量的图片,但是这些图片在内存中的存储如果不合理就会导致Crash堆栈然后是疯狂GC,接着触发我们前面说到的GC for Alloc,导致Stop-the-world的“卡”,最后的结果就是导致功能异常,有损用户体验。


既然有这么多的损害,为什么不能把图片下载来都放到磁盘(SD Card)上呢?其实答案不难猜,放在内存中,展示起来会“快”那么一些,快的原因有如下两点:

  • 硬件快(内存本身读取、存入速度快);
  • 复用快(解码成果有效保存,复用时,直接使用解码后对象,而不是再做一次图片解码);


很多同学不知道所谓“解码”的概念,可以简单地理解,Android系统要在屏幕上展示图片的时候只认“像素缓冲”,而这也是大多数操作系统的特征。我们常见的jpg、png等图片格式,都是把“像素缓冲”使用不同的手段压缩后的结果,所以相对而言,这些格式的图片,要在设备上展示,就必须经过一次“解码”,它的执行速度会受图片压缩比、尺寸等因素影响,是影响图片展示速度的一个重要因素。


因此官方建议使用LRU算法来做图片缓存,而不是之前推荐的WeekReference,因为WeekReference会导致大量GC。另外官方也建议,把从内存淘汰的图片,降低压缩比存储到本地,以备后用。这样就可以最大限度地降低以后复用时的解码开销。


现在我们来归纳一下,内存问题主要包括常驻问题(主要是图片缓存)、泄漏问题(主要是Activity泄漏)、GC问题(关键是GC For Alloc),后果会导致App Crash、闪退、后台被杀、卡顿,而且这是各种资源类性能问题积压的最后一环。因此可见其重要性,下面,我们来介绍一下如何简单快速的检测和定位内存泄漏问题。

方案



这里介绍手工和自动两种检测方案


手工检测和定位


先介绍一个命令:


$ adb shell dumpsys meminfo (pid name)

这个命令是用来查看指定进程所占用内存的具体情况,比如当前APP在手机中占用的具体的堆内存大小、View数量、Activity数量等:


微信图片_20220518230450.jpg


其中Activities的数量是一个非常关键的信息,可以帮助我们快速找出内存泄漏的页面,我们可以反复进入待测页面,如果反复进入退出后,查询内存的占用情况,Activity数量一直在增加,那说明一定是发生内存泄漏了。


在确定了哪个页面发生内存泄漏后,用Android Studio 自带工具就可以直接分析泄漏的Activity,完全没必要再单独安装MAT了,如下图打开Android Studio 的profile进入内存模块:


微信图片_20220518230455.png

点击Dump,反复进入退出发生内存泄漏的页面


微信图片_20220518230459.jpg

勾选下面的Activity/Fragment Leaks 就可以展示出具体哪些Activity或Fragment发生了内存泄漏,右边还有具体的引用情况


微信图片_20220518230507.jpg

自动检测和定位

这里主要是通过leakcanary来实现的,leakcanary具体在客户端的接入就不多说了,可以参考官网的文档:https://square.github.io/leakcanary/getting_started/


这里主要想讲一下如何自动收集leakcanary检测出的内存泄漏信息,因为在日常测试和开发过程中,即便客户端接了内存泄漏检测的工具,但也只是作为一个debug工具,很难系统的看出某个版本的应用内存泄漏情况是如何的。


于是我们需要在业务和开发同学平时使用的过程中顺带将这些信息收集上来,在同一的平台上以版本和页面为维度去展示,可以直观的看到某个版本发生了多少次内存泄漏以及哪些页面的哪些调用栈。


首先新建一个LeakUploadService类,用来格式化内存泄漏详情以及上传到日志服务器便于快速定位,具体代码如下:

public class LeakUploadService extends DisplayLeakService {
    @Override
    protected void afterDefaultHandling(HeapDump heapDump, AnalysisResult result, String leakInfo) {
        if (!result.leakFound || result.excludedLeak) {
            return;
        }
        String className = result.className;
        String pkgName = leakInfo.trim().split(":")[0].split(" ")[1];
        String pkgVer = leakInfo.trim().split(":")[1];
        String leakDetail = leakInfo.split("\n\n")[0] + "\n\n" + leakInfo.split("\n\n")[1];
        JSONObject json = new JSONObject();
        try {
            json.put("className", className);
            json.put("app", pkgName);
            json.put("appVersion", pkgVer);
            json.put("leakDetail", leakDetail);
        } catch (JSONException e) {
            e.printStackTrace();
        }
        OkHttpClient okHttpClient = new OkHttpClient();
        MediaType mediaType = MediaType.parse("application/json; charset=utf-8");
        RequestBody requestBody = RequestBody.create(mediaType, json.toString());
        Request request = new Request.Builder()
                .url("日志上传接口")
                .post(requestBody)
                .build();
        try {
            okHttpClient.newCall(request).execute();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

然后在manifest中注册:

<service android:name="com.squareup.leakcanary.LeakUploadService"/>


最后将Service注册到监听接口:

LeakCanary.refWatcher(application).listenerServiceClass(LeakUploadService.class)


相关实践学习
通过性能测试PTS对云服务器ECS进行规格选择与性能压测
本文为您介绍如何利用性能测试PTS对云服务器ECS进行规格选择与性能压测。
相关文章
|
21天前
|
缓存 Java 测试技术
谷粒商城笔记+踩坑(11)——性能压测和调优,JMeter压力测试+jvisualvm监控性能+资源动静分离+修改堆内存
使用JMeter对项目各个接口进行压力测试,并对前端进行动静分离优化,优化三级分类查询接口的性能
谷粒商城笔记+踩坑(11)——性能压测和调优,JMeter压力测试+jvisualvm监控性能+资源动静分离+修改堆内存
|
23天前
|
测试技术 Shell Android开发
Android 性能测试初探 (六)
本节聊聊性能测试的最后一项- 流量,当然我所指的性能测试是针对大部分应用而言的,可能还有部分应用会关注网速、弱网之类的测试,但本系列文章都不去一一探讨了。
38 6
|
23天前
|
JavaScript 测试技术 Android开发
Android 性能测试初探 (四)
本文介绍了GPU在移动端性能测试中的重要性,并详细解释了过度绘制、帧率和帧方差的概念。针对GPU测试,文章列举了三项主要测试内容:界面过度绘制、屏幕滑动帧速率和平滑度。其中,过度绘制测试需遵循特定标准,而帧速率和平滑度测试则可通过软件或硬件方法实现。在软件测试中,使用Systrace插件和高速相机是两种常用手段。对于不同机型,帧率及帧方差的测试标准也需相应调整。
33 5
|
23天前
|
测试技术 Shell Android开发
Android 性能测试初探 (三)
本文承接《Android性能测试初探(二)》,深入探讨CPU与内存测试。介绍了移动端内存测试的重要性及其测试目标,并详细列举了不同状态下应用内存消耗情况的测试项目。此外,还提供了多种内存测试方法,包括使用`procrank`等工具的具体操作步骤。最后,文章也简要提及了CPU测试的相关内容,帮助读者更好地理解Android性能测试的关键要素。
36 5
|
23天前
|
测试技术 Shell 定位技术
Android 性能测试初探 (五)
聊聊大家不常关注的测试项- 功耗
36 3
|
23天前
|
算法 测试技术 Android开发
Android 性能测试初探 (二)
上回大体介绍了下在 android 端的性能测试项,现在我们就细节测试项做一些阐述(包括如何自己 DIY 测试)
32 4
|
23天前
|
测试技术 API Android开发
Android 性能测试初探 (一)
Android 性能测试,跟pc性能测试一样分为客户端及服务器,但在客户端上的性能测试分为 2 类: 一类为 rom 版本的性能测试;一类为应用的性能测试。
39 3
|
21天前
|
监控 算法 数据可视化
深入解析Android应用开发中的高效内存管理策略在移动应用开发领域,Android平台因其开放性和灵活性备受开发者青睐。然而,随之而来的是内存管理的复杂性,这对开发者提出了更高的要求。高效的内存管理不仅能够提升应用的性能,还能有效避免因内存泄漏导致的应用崩溃。本文将探讨Android应用开发中的内存管理问题,并提供一系列实用的优化策略,帮助开发者打造更稳定、更高效的应用。
在Android开发中,内存管理是一个绕不开的话题。良好的内存管理机制不仅可以提高应用的运行效率,还能有效预防内存泄漏和过度消耗,从而延长电池寿命并提升用户体验。本文从Android内存管理的基本原理出发,详细讨论了几种常见的内存管理技巧,包括内存泄漏的检测与修复、内存分配与回收的优化方法,以及如何通过合理的编程习惯减少内存开销。通过对这些内容的阐述,旨在为Android开发者提供一套系统化的内存优化指南,助力开发出更加流畅稳定的应用。
43 0
|
存储 编解码 Android开发
Android内存优化-Bitmap内存优化
在日常开发中,我们不免会使用到Bitmap,而bitmap确实实在在的是内存使用的 “大户”,如何更好的使用 bitmap,减少其对 App内存的使用,是我们开发中不可回避的问题。
180 0
Android内存优化-Bitmap内存优化
|
存储 编解码 缓存
下一篇
无影云桌面