支付宝客户端架构解析:Android 客户端启动速度优化之「垃圾回收」

本文涉及的产品
mPaaS订阅基础套餐,标准版 3个月
简介: 本文将介绍支付宝 Android 客户端启动速度优化下的「垃圾回收」具体思路。 应用启动时间是移动 App 一个重要的用户体验环节,相对于普通的移动 App,支付宝过于庞大,必然会影响启动速度,一些常规的优化手段在支付宝中已经做得比较完善了,本篇文章尝试从 GC 的层面来进一步优化支付宝的启动速度。

前言

《支付宝客户端架构解析》系列将从支付宝客户端的架构设计方案入手,细分拆解客户端在“容器化框架设计”、“网络优化”、“性能启动优化”、“自动化日志收集”、“RPC 组件设计”、“移动应用监控、诊断、定位”等具体实现,带领大家进一步了解支付宝在客户端架构上的迭代与优化历程。

本节将介绍支付宝 Android 客户端启动速度优化下的「垃圾回收」具体思路。

应用启动时间是移动 App 一个重要的用户体验环节,相对于普通的移动 App,支付宝过于庞大,必然会影响启动速度,一些常规的优化手段在支付宝中已经做得比较完善了,本篇文章尝试从 GC 的层面来进一步优化支付宝的启动速度。

背景

相对于 C 语言来说,Java 语言有一些特性,例如开发人员不用考虑内存的分配和回收,然而,进程内存管理又是必不可少的环节,妥协的结果是 Java 语言的设计者们把对象分配和回收放到了 Java虚拟机,这里希望明确一个概念:GC 是有代价的,这个代价包括:阻塞 Java 程序的执行,占用 CPU 资源,占用额外内存等,谷歌的工程师意识到了 GC 对应用的影响,所以把 GC 的日志默认输出到了 Logcat,我们经常能够看到 Logcat 里输出以下几种 GC 日志:

  1. GC_EXPLICIT:Dalivk 给开发人员提供的主动触发 GC 的 API,读者可以参看 Google Maps 的设计来体会这个 API 的用法
  2. GC_FOR _ALLOCK:是分配对象失败时触发的 GC,这个 GC 会将应用所有的 Java 线程暂停运行,直到 GC 结束。
  3. GC_CONCURRENT:是 Java 虚拟机根据堆的当前状态触发的 GC,这个 GC 在 Dalvik 单独 GC 线程里运行,在部分时间里不影响应用 Java 线程的运行。

支付宝启动是一个典型的关键路径场景,我们希望看到尽可能少的 GC_ CONCURRENT(如果可能,GC_ FOR_ ALLOCK 也应该缩减到最少),然而,通过 Logcat 我们会看到非常糟糕的 GC 行为—大量的 GC_ FOR_ ALLOCK 以及触目惊心的 Java 线程被 WAIT_ FOR_ CONCURRENT_ GC 阻塞,如下图所示,通过简单统计这些GC消耗的时间,我们能够得出GC严重影响应用启动时间的结论。

gc_log

设计思路

支付宝是 Android 系统的一个应用程序,如何能够通过影响 Dalvik 的 GC 行为来缩短启动时间呢?这个问题可以分解为两步:

  • 支付宝是否能影响自身 Dalvik 的行为
  • 如何改进 Dalvik,缩短启动时间

第一个问题答案是肯定的,Android 系统的设计思路是每个 Android 应用程序都有独立的 Dalvik 实例,应用启动后可以修改自己的进程空间里的代码和数据,因此支付宝通过修改内存中的 Dalvik 库文件 libdvm.so 影响 Dalvik 的行为。

第二个问题的难点在于投入产出比:修改进程空间的代码和数据是面向二进制,难度远远大于源代码,也就是说稍微复杂的 Dalvik 改进工作是不可能的。

基于以上两点,提出了一种设想:启动时 GC 抑制,允许堆一直增长,直到开发人员主动停止 GC 抑制或者 OOM 停止 GC 抑制,这是一种"空间换时间"策略,用更多的内存消耗来换取启动时间的缩短,这种策略可行有两个前提:一是设备厂商没有加密内存中的 Dalvik 库文件,二是设备厂商没有改动 Google 的 Dalvik 源码(或者少量的改动),理论上通过白名单的方式可以覆盖所有设备,但是实现和维护成本都非常高。

GC 抑制的实现

GC 抑制的前提是 Dalvik 比较熟悉,知道如何改变 GC 的行为,解决方案大致如下:首先在源码级别找到抑制GC的修改方法,例如改变跳转分支,其次,在二进制代码里找到 A 分支条件跳转的"指令指纹",以及用于改变分支的二进制代码,假设为 override_A,应用启动后扫描内存中的 libdvm.so,根据"指令指纹"定位到修改位置,然后用 override_A 覆盖,这里需要注意的是,"指令指纹"的定义需要有一些编译器和 arm 指令集知识,实现 GC 抑制主要实现了以下 4 个部分:

  • 取消 softlimit 检测
  • 取消 GC 线程的唤醒
  • 取消 GC 例程函数
  • OOM 停止 GC 抑制的实现

1. 取消 softlimit 检测:

取消 softlimit 检测的目的是最大限度的分配对象,下图为 softlimit 检查对应的 arm 指令片段,位于 dvmHeapSourceAlloc 函数中,OXE057 对应于"return NULL"的分支,如果我们想永远不进入"return NULL"分支,可以改变 cmp 指令的结果,在具体实现里我们把"0X42"作为"指令指纹"来识别而且修改为 "cmp r0, r0",这样就可以实现取消 softlimit 检查。

   7616c: 42a1 cmp r1, r4
   7616e: d901 bls.n 76174 <_Z18dvmHeapSourceAllocj+0x20>
   76170: 2400 movs r4, #0
   76172: e057 b.n 76224 <_Z18dvmHeapSourceAllocj+0xd0>
   76174: f8df 90bc ldr.w r9, [pc, #188] ; 76234    <_Z18dvmHeapSourceAllocj+0xe0>
   76178: 6a28 ldr r0, [r5, #32]
   7617a: f853 3009 ldr.w r3, [r3, r9]
   7617e: 7d1a ldrb r2, [r3, #20]
void* dvmHeapSourceAlloc(size_t n)
{
...
if (heap->bytesAllocated + n > hs->softLimit) {
/*
* This allocation would push us over the soft limit; act as
* if the heap is full.
/
return NULL;
AI 代码解读

2. 取消GC线程的唤醒

取消 GC 线程唤醒的目的是防止 GC 线程频繁唤醒导致的线程抖动。下图是对应的 C++ 代码和 arm 指令片段,这段代码同样位于 dvmHeapSourceAlloc 函数中。在具体实现里我们会依次扫描 libdvm.so 的 dynstr、dynsym、rel.plt 和 plt 区域获取 pthreadcondsignal@plt 的地址,然后遍历 dvmHeapSourceAlloc 中的所有分支跳转,计算跳转目的地址。

如果发现 pthreadcondsignal@plt 和当前分支跳转目的地址配置,擦除这条指令即可。

   if (heap->bytesAllocated > heap->concurrentStartBytes) {
/
* We have exceeded the allocation threshold. Wake up the
* garbage collector.
*/
dvmSignalCond(&gHs->gcThreadCond);
}
7621c: 6800 ldr r0, [r0, #0]
7621e: 30b4 adds r0, #180 ; 0xb4
76220: f7a9 ed0e blx 1fc40 
76224: 4620 mov r0, r4
76226: e8bd 83f8 ldmia.w sp!, {r3, r4, r5, r6, r7, r8, r9, pc}
AI 代码解读

3. 取消GC例程函数

取消 GC 例程函数采用钩子技术来实现,我们将 GC 抑制封装成了两个 native 接口 doStartSuppressGCdoStopSuppressGC;并且进一步封装为 JNI 接口,便于开发者在 Java 里调用。一般的应用方式是,开发者通过日志看到支付宝在某个场景会触发大量的 GC 且这个 GC 影响用户体验(响应时间慢或者动画卡顿),然后在这个场景前后插入 doStartSuppressGCdoStopSuppressGC

以支付宝冷启动场景为例,我们在容器 Quinox 的 attachBaseContext 函数里插入 doStartSuppressGC,在首页加载结束时插入 doStopSuppressGC

4. OOM 停止GC抑制的实现

如果仅仅考虑在支付宝启动过程中抑制 GC,不需要考虑 OOM 停止 GC 抑制的实现,因为支付宝启动不足以触发 OOM。但是我们希望 GC 抑制成为一个基础模块,能够应用到更多场景中。如果程序在调用 doStopSuppressGC 前触发了 OOM,则需要在 OOM 发生前停止 GC 抑制。和前面简单的改变分支跳转方向不同,需要在 OOM 发生前注入一个新的的分支跳转,这个新分支的代码由我们来实现。新分支主要功能是,调用 doStopSuppressGC,然后去掉注入的新分支,最后跳回 Dalvik 执行 OOM。

gc_oom

实现同样采用传统的钩子技术。在钩子函数 dvmCollectGarbageInternal 里:

  • 当条件不满足时直接返回,达到取消 GC 的目的;
  • 条件满足时,取消钩子且执行原来的 dvmCollectGarbageInternal

实现中使用了开源的二进制注入框架:https://github.com/crmulliner/adbi

这里需要注意的是,在热点函数里使用这个框架提供的 pre_hookpost_hook 的性能开销非常大。

本文里的设计只会用到一次 pre_hook,所以不存在性能问题。
看到的这里读者可能会问,这种通过“指令指纹”的方式靠谱么?我的答案是,漏判不影响正确性,误判理论上存在但概率极小(误判指“指令指纹”定位到错误代码位置)。即使误判发生了,我们还有最后一层保障——基础架构组同学实现的容灾机制。当误判导致程序异常无法完成正常启动时,重启支付宝而且在后续的启动中直接放弃 GC 抑制。

效果

effect

上图的启动时间的数据是在内部的 Android 4.x 测试设备上获得的(没有标注 release 表示 debug 版本)。从图表上来看,支付宝客户端的启动时间缩短了 15%~30%。

小结

通过本节内容,我们初步了解了支付宝在 Android 客户端启动性能优化下的「垃圾回收」机制和具体实践,由于篇幅限制,很多技术要点我们无法一一展开。而相应的技术内核,我们同样应用在了 mPaaS 并对外输出,欢迎大家上手体验:

https://tech.antfin.com/docs/2/49549

关于 Android 端启动性能优化的设计思路和具体实践,同样期待你们的反馈,欢迎一起探讨交流。

往期阅读

《支付宝客户端架构解析:Android容器化框架初探》

关注我们微信公众号「mPaaS」,获得第一手 mPaaS 技术实践干货

目录
打赏
0
0
0
0
38
分享
相关文章
优化管理与服务:操作系统控制平台的订阅功能解析
本文介绍了如何通过操作系统控制平台提升系统效率,优化资源利用。首先,通过阿里云官方平台开通服务并安装SysOM组件,体验操作系统控制平台的功能。接着,详细讲解了订阅管理功能,包括创建订阅、查看和管理ECS实例的私有YUM仓库权限。订阅私有YUM仓库能够集中管理软件包版本、提升安全性,并提供灵活的配置选项。最后总结指出,使用阿里云的订阅和私有YUM仓库功能,可以提高系统可靠性和运维效率,确保业务顺畅运行。
深入解析MySQL的EXPLAIN:指标详解与索引优化
MySQL 中的 `EXPLAIN` 语句用于分析和优化 SQL 查询,帮助你了解查询优化器的执行计划。本文详细介绍了 `EXPLAIN` 输出的各项指标,如 `id`、`select_type`、`table`、`type`、`key` 等,并提供了如何利用这些指标优化索引结构和 SQL 语句的具体方法。通过实战案例,展示了如何通过创建合适索引和调整查询语句来提升查询性能。
650 9
Resume Matcher:增加面试机会!开源AI简历优化工具,一键解析简历和职位描述并优化
Resume Matcher 是一款开源AI简历优化工具,通过解析简历和职位描述,提取关键词并计算文本相似性,帮助求职者优化简历内容,提升通过自动化筛选系统(ATS)的概率,增加面试机会。
121 18
Resume Matcher:增加面试机会!开源AI简历优化工具,一键解析简历和职位描述并优化
深入解析Tiktokenizer:大语言模型中核心分词技术的原理与架构
Tiktokenizer 是一款现代分词工具,旨在高效、智能地将文本转换为机器可处理的离散单元(token)。它不仅超越了传统的空格分割和正则表达式匹配方法,还结合了上下文感知能力,适应复杂语言结构。Tiktokenizer 的核心特性包括自适应 token 分割、高效编码能力和出色的可扩展性,使其适用于从聊天机器人到大规模文本分析等多种应用场景。通过模块化设计,Tiktokenizer 确保了代码的可重用性和维护性,并在分词精度、处理效率和灵活性方面表现出色。此外,它支持多语言处理、表情符号识别和领域特定文本处理,能够应对各种复杂的文本输入需求。
78 6
深入解析Tiktokenizer:大语言模型中核心分词技术的原理与架构
阿里云服务器架构解析:从X86到高性能计算、异构计算等不同架构性能、适用场景及选择参考
当我们准备选购阿里云服务器时,阿里云提供了X86计算、ARM计算、GPU/FPGA/ASIC、弹性裸金属服务器以及高性能计算等多种架构,每种架构都有其独特的特点和适用场景。本文将详细解析这些架构的区别,探讨它们的主要特点和适用场景,并为用户提供选择云服务器架构的全面指南。
128 18
【Android】网络技术知识总结之WebView,HttpURLConnection,OKHttp,XML的pull解析方式
本文总结了Android中几种常用的网络技术,包括WebView、HttpURLConnection、OKHttp和XML的Pull解析方式。每种技术都有其独特的特点和适用场景。理解并熟练运用这些技术,可以帮助开发者构建高效、可靠的网络应用程序。通过示例代码和详细解释,本文为开发者提供了实用的参考和指导。
52 15
Android调试终极指南:ADB安装+多设备连接+ANR日志抓取全流程解析,覆盖环境变量配置/多设备调试/ANR日志分析全流程,附Win/Mac/Linux三平台解决方案
ADB(Android Debug Bridge)是安卓开发中的重要工具,用于连接电脑与安卓设备,实现文件传输、应用管理、日志抓取等功能。本文介绍了 ADB 的基本概念、安装配置及常用命令。包括:1) 基本命令如 `adb version` 和 `adb devices`;2) 权限操作如 `adb root` 和 `adb shell`;3) APK 操作如安装、卸载应用;4) 文件传输如 `adb push` 和 `adb pull`;5) 日志记录如 `adb logcat`;6) 系统信息获取如屏幕截图和录屏。通过这些功能,用户可高效调试和管理安卓设备。
地铁站内导航系统解决方案:技术架构与核心功能设计解析
本文旨在分享一套地铁站内导航系统技术方案,通过蓝牙Beacon技术与AI算法的结合,解决传统导航定位不准确、路径规划不合理等问题,提升乘客出行体验,同时为地铁运营商提供数据支持与增值服务。 如需获取校地铁站内智能导航系统方案文档可前往文章最下方获取,如有项目合作及技术交流欢迎私信我们哦~
57 1
Android实战经验之Kotlin中快速实现MVI架构
MVI架构通过单向数据流和不可变状态,提供了一种清晰、可预测的状态管理方式。在Kotlin中实现MVI架构,不仅提高了代码的可维护性和可测试性,还能更好地应对复杂的UI交互和状态管理。通过本文的介绍,希望开发者能够掌握MVI架构的核心思想,并在实际项目中灵活应用。
60 8
静态长效代理IP利用率瓶颈解析与优化路径
在信息化时代,互联网已深度融入社会各领域,HTTP动态代理IP应用广泛,但静态长效代理IP利用率未达百分百,反映出行业结构性矛盾。优质IP资源稀缺且成本高,全球IPv4地址分配殆尽,高质量IP仅占23%。同时,代理服务管理存在技术瓶颈,如IP池更新慢、质量监控缺失及多协议支持不足。智能调度系统也面临风险预判弱、负载均衡失效等问题。未来需构建分布式IP网络、引入AI智能调度并建立质量认证体系,以提升资源利用率,推动数字经济发展。
39 2

热门文章

最新文章

推荐镜像

更多