大对象

本文档介绍了EMAS应用监控的大对象监控,详细说明了如何使用本功能。

概述

频繁或不必要地分配大内存对象,会显著增加应用的内存压力,导致 UI 卡顿,并提高 OOM 风险。EMAS 大对象监控功能能够实时捕获应用中超过预设大小阈值的对象分配事件,并记录其分配堆栈和引用信息。这使得开发者可以主动优化内存使用,例如通过复用对象、使用更轻量级的数据结构或在子线程进行耗时操作,从而提升应用的流畅度和稳定性。

大对象原因与表现

大对象指单次内存分配超过预设阈值的对象或数据块。大对象分配本身并非错误,频繁或不合理的大内存分配申请会引发以下问题:

  • App内存使用量显著上升

  • 频繁的内存分配与回收增加CPU开销,导致掉帧、动画卡顿

  • 最终引发OOM崩溃

常见原因有:

Android

下面简单列举几种常见的造成大对象的场景。

  • 大规模数据结构

    long[] bigData = new long[128 * 1024]; //1Mb
  • 位图处理

    // 未压缩的高清图片
    Bitmap highResBitmap = BitmapFactory.decodeResource(res, R.drawable.huge_image);
    // 1920x1080 ARGB_8888 格式 = 1920*1080*4 ≈ 7.9MB
  • 文件操作

    // 一次性读取大文件
    byte[] fileData = new byte[(int) largeFile.length()]; // 可能几十MB
  • 缓存设计不当

    // 无限制的内存缓存
    memoryCache = new LruCache<String, Bitmap>(Integer.MAX_VALUE); // 可能耗尽内存

iOS

下面简单列举几种常见的造成大对象的场景。

  • 加载超大尺寸图片(UIImage/CGImage)

    // big_8000x8000.png 约 8000 × 8000 × 4 ≈ 244 MB
    NSString *path = [[NSBundle mainBundle] pathForResource:@"big_8000x8000"
                                                     ofType:@"png"];
    UIImage *hugeImage = [UIImage imageWithContentsOfFile:path];
    // imageNamed: 会进入系统缓存,占用时间更长
    self.imageView.image = hugeImage;
  • 离屏渲染/位图上下文开得过大

    CGSize canvasSize = CGSizeMake(10000, 10000);     // 10000² × 4 ≈ 381 MB
    UIGraphicsBeginImageContextWithOptions(canvasSize, NO, 1.0);
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    [[UIColor redColor] setFill];
    CGContextFillRect(ctx, CGRectMake(0, 0, canvasSize.width, canvasSize.height));
    UIImage *snapshot = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    // snapshot 仍在内存中
  • 大型数据文件解析(JSON/XML)

    NSData *jsonData = [NSData dataWithContentsOfURL:hugeJSONURL];
    NSDictionary *jsonObj = [NSJSONSerialization JSONObjectWithData:jsonData
                                                            options:kNilOptions
                                                              error:nil];
    // jsonData + jsonObj + 生成的子字典/数组 => 双倍甚至数倍内存
  • Core Data/数据库全量加载

    NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Person"];
    request.returnsObjectsAsFaults = NO;   // 注意:关闭 faulting!
    NSArray *allPersons = [context executeFetchRequest:request error:nil];
    // 如果表有十几万行,每行若有多张头像/大字段,内存瞬间暴涨

大对象检测方式

Android

Android当前版本支持Java大对象的检测,检测手段是分析堆转储(Heap Dumps)文件,找出堆转储文件中超过1Mb的对象。

iOS

SDK 实时监控应用的内存分配行为。当单次分配的内存大小超过预设阈值时,即视为一次大对象分配事件。

  • 监控阈值: 默认为 10MB,支持您根据业务需求进行自定义配置。

  • 诊断信息: 对于每一次捕获到的大对象分配事件,SDK 将记录以下关键信息:

    • 分配大小: 精确记录本次分配的内存字节数。

    • 分配调用栈 : 提供完整的分配操作调用栈,帮助您追溯分配源头,评估其业务合理性。

准备工作

Android

已按照Android SDK接入文档接入了内存分析。

iOS

已按照iOS SDK接入文档接入了内存分析。

功能说明

趋势分析

问题趋势展示筛选条件下,大对象指标的趋势图,查看大对象问题的波动和影响面。

image

指标

指标说明

问题数

发生大对象问题的次数

影响设备数

发生大对象问题的设备数量(按设备去重的问题数)

问题率

问题数/设备总启动次数

影响设备率

影响设备数(按设备去重的问题数)/启动总设备数(按设备去重的启动次数)

分布分析

分布分析支持通过多维统计(如应用版本、操作系统版本、机型等)来观测大对象问题发生的分布情况以及定位问题。

image

  • 默认以应用版本、系统版本、机型、品牌四个维度展示大对象分布情况,支持点击“分布维度”按钮下拉勾选维度替换默认维度,最少选择1个,最多选择4个。

  • 点击image.png可以切换视图:分布排行、列表排行。

排行分析

排行分析展示大对象问题的排行情况,包含当天所有类型TOP10、当天新增类型TOP10、占比变化TOP10,帮助开发者聚焦当天变化最大的问题类型。

问题列表

问题列表展示了聚合后的大对象问题类型,包括发生问题数、影响设备数、对象大小P50/P70/P90(此问题中大对象的Retained Size分位值)、首现版本和问题状态。

image

  • 排序方式:指标支持排序,默认按照问题数从高到低排序。

  • 查看详情:点击问题名称,进入到对应的问题详情分析页。

问题详情

问题详情支持针对具体大对象问题做详细下钻分析,提供该问题汇总的基本信息、趋势分析、分布分析和引用链/分配堆栈等问题分析能力,并提供每一次客户端上报的详细信息。

基本信息

image

参数

说明

错误摘要

错误类型名称

聚合ID

根据错误的特征生成的64位唯一ID

问题数

问题数=所选时间段内该页面发生大对象的总次数

问题率

问题率=筛选条件下问题数/筛选条件下设备总启动次数

影响设备数

影响用户数=筛选条件下发生大对象的设备数量

影响设备率

影响设备率=筛选条件下影响设备数(按设备去重的问题数)/筛选条件下启动总设备数(按设备去重的启动次数)

首次发生

所选时间段内首次发生的时间

最近发生

所选时间段内最近发生的时间

首现版本

首次在哪个版本出现此问题

统计版本

出现此问题统计的版本

标签设置

对此错误类型添加标签,便于管理

一个错误信息最多支持10个标签,一个标签最多显示15个字符

问题状态

问题状态支持修改,便于排查和追踪问题是否解决

  • NEW:新出现

  • FIXED:已被修复

  • OPEN:被修复后再次出现

  • CLOSE:已被关闭

详细信息

展示同一大对象问题的所有客户端上报实例。左侧按照问题发生的时间顺序排列,点击后右侧展示此次大对象问题的详细上报信息,包含引用链(Android)/内存分配堆栈(iOS)、现场数据等。

引用链(Android)

引用链会展示大对象到具体GC Root的具体引用关系,可以判断引用关系是否合理,是否可以断掉。

image

分配堆栈(iOS)

image

现场数据

现场数据还原了此条大对象问题的现场信息,包括基础信息、内存信息和自定义数据。现场数据字段定义与崩溃分析的现场数据类似,请参考查看现场数据文档。

治理技巧

大对象问题是典型的“积少成多”或“瞬间爆炸”型内存杀手。治理的关键在于主动发现并优化不合理的内存分配。EMAS平台通过捕获大对象的分配现场和持有关系,为您提供了从被动响应OOM到主动优化内存的工具。

Android端,一个大对象如果能被及时回收,通常问题不大。但如果它被一个长生命周期的对象持有,就会长时间占据内存,成为隐患。引用链 正是用来回答“谁在持有它?”这个问题的,以如上Android引用链截图为例进行分析。

  1. 识别大对象与持有路径

    • 大对象(链的顶端):查看引用链的顶部,如截图中的 android.graphics.Bitmap,大小为4.34MB。这便是我们关注的目标。

    • GC Root(链的底端):定位到链条末端的 GC Root 标签。这代表了持有关系的源头。

  2. 自下而上,分析引用关系 我们推荐从引用链的 底部向上反推,来理解整个持有路径:

    • 在截图中,GC Root 是一个 Thread。

    • 这个线程通过层层引用,最终持有了 com.taobao.application.common.impl.b 这个应用实例。

    • 该实例又持有了 MemMonitorActivity。

    • Activity 通过 ViewBinding 持有了一个 ImageView。

    • 最终,ImageView 通过它的 Drawable 状态,持有了这个4.34MB的 Bitmap。

  3. 定位问题并决策

    • 结论:这个大Bitmap之所以存在于内存中,是因为它正被一个ActivityImageView显示。

    • 下一步行动:现在,您需要评估这个持有关系是否合理。这个图片是否太大?是否可以在显示前进行压缩?这个Activity销毁时,这个引用链能否被正常断开?分析的重点就落在了 MemMonitorActivity 的图片加载逻辑上。

iOS端,ARC无法管理底层的内存块(如通过 mmap 分配的内存),这些通常是造成大对象问题的根源。分配堆栈 能精准告诉您,是哪段代码触发了这次大内存分配,以如上iOS分配堆栈截图为例进行分析。

  1. 识别分配源头

    • 关注应用自身的代码:在 分配堆栈 列表中,您需要从上到下寻找属于您自己应用的代码。在截图中,AlicloudApmAll-TestApp.debug.dylib 就是关键线索。

    • 结合系统库上下文:堆栈中的系统库(如UIKitCore、libsystem_kernel.dylib)为您提供了上下文。这表明这次大内存分配很可能是由UI相关的操作(如渲染、图片解码)最终触发的系统底层调用。

  2. 解读堆栈,还原业务场景

    • 在截图中,虽然堆栈是符号化的地址,但通过观察 AlicloudApmAll-TestApp.debug.dylib 和 UIKitCore 的函数调用,可以推断:

    • 可能场景1:代码正在通过UIImageCGImage相关API加载或处理一张非常大的图片,导致UIKit在底层通过mmap分配了一大块内存来存放解码后的位图数据。

    • 可能场景2:代码正在处理一个大型文件(如视频、音频、数据库文件),直接将其映射到了内存中。

  3. 定位问题代码

    • 下一步行动:您需要将这些符号化的地址在本地通过符号表(dSYM)进行还原,就能看到具体的函数名。然后,审查这些函数中的代码,检查是否存在加载未经压缩的大图、一次性读取大文件、创建超大画布等行为。