Android App性能评测分析-内存篇

简介: 1、内存了解在Android App的性能优化的各个部分里,内存方面的知识较多且不易理解,内存的问题绝对是最令人头疼的一部分,需要对内存基础知识、内存分配、内存管理机制等非常熟悉,才能排查问题。

1、内存了解

在Android App的性能优化的各个部分里,内存方面的知识较多且不易理解,内存的问题绝对是最令人头疼的一部分,需要对内存基础知识、内存分配、内存管理机制等非常熟悉,才能排查问题。

1.1 了解进程的地址空间

在32位操作系统中,进程的地址空间为0到4GB,这里主要说明一下Stack和Heap:
Stack空间(进栈和出栈):

由操作系统控制,其中主要存储函数地址、函数参数、局部变量等等,所以Stack空间不需要很大,一般为几MB大小。

Heap空间:

它的使用由程序员控制,程序员可以使用malloc、new、free、delete等函数调用来操作这片地址空间。Heap为程序完成各种复杂任务提供内存空间,所以空间比较大,一般为几百MB到几GB。正是因为Heap空间由程序员管理,所以容易出现使用不当导致严重问题。

进程地址空间-引用自CSDN.png

1.2 Android的内存管理

Android系统的ART和Dalvik虚拟机扮演了常规的内存垃圾自动回收的角色, 使用paging 和 memory-mapping来管理内存,这意味着不管是因为创建对象还是使用使用内存页面造成的任何被修改的内存,都会一直存在于内存中,App唯一释放内存的方法就是释放App持有的对象引用,使GC可以回收。

1.2.1 Android的应用进程按共享/私有分类如下:

首先来了解什么是共享内存

Android应用的进程都是从一个叫做Zygote的进程fork出来的。Zygote进程在系统启动并且载入通用的framework的代码与资源之后开始启动。为了启动一个新的程序进程,系统会fork Zygote进程生成一个新的进程,然后在新的进程中加载并运行应用程序的代码。这使得大多数的RAM pages被用来分配给framework的代码,同时使得RAM资源能够在应用的所有进程之间进行共享。
- 大多数static的数据被mmapped到一个进程中。这不仅仅使得同样的数据能够在进程间进行共享,而且使得它能够在需要的时候被paged out。常见的static数据包括Dalvik Code,app resources,so文件等。
- 大多数情况下,Android通过显式的分配共享内存区域(例如ashmem或者gralloc)来实现动态RAM区域能够在不同进程之间进行共享的机制。例如,Window Surface在App与Screen Compositor之间使用共享的内存,Cursor Buffers在Content Provider与Clients之间共享内存。


image.png

共享内存:Dalvik虚拟机代码、应用框架的代码、应用框架的资源
应用框架的SO库。
私有内存:应用的代码、应用的资源、应用的SO库
共享/私有内存:堆内存,其他部分

1.2.2 android进程中内存分类如下:

android进程中内存分类.png

native heap:是lib层C/C++库所占用的内存(Native代码分配的内存,虚拟机和Android框架本身也会分配),不包含dalvik实例的linux进程,/system/bin/目录下面的程序文件运行后都是以native进程形式存在的。
Dalvik heap:Dalvik虚拟机使用的内存,包含dalvik-heap和dalvik-zygote,堆内存,是java实例对象的空间
以上两个heap空间完全由程序员控制,是最主要的两块内存,另外还有下面3种:
Dalvik Other:类的数据结构和索引
so mmap:Native代码和常量
dex mmap:Java代码和常量

1.2.3 查看内存占用

通过命令行adb shell dumpsys meminfo packagename查看内存详细占用情况:

其中几个关键的数据:

  • Private(Clean和Dirty的):应用进程单独使用的内存,代表着系统杀死你的进程后可以实际回收的内存总量**。通常需要特别关注其中更为昂贵的dirty部分,它不仅只被你的进程使用而且会持续占用内存而不能被从内存中置换出存储。申请的全部Dalvik和本地heap内存都是Dirty的,和Zygote共享的Dalvik和本地heap内存也都是Dirty的。
  • Dalvik Heap:Dalvik虚拟机使用的内存,包含dalvik-heap和dalvik-zygote,堆内存,所有的Java对象实例都放在这里。
  • Heap Alloc:累加了Dalvik和Native的heap。
  • PSS:这是加入与其他进程共享的分页内存后你的应用占用的内存量,你的进程单独使用的全部内存也会加入这个值里,多进程共享的内存按照共享比例添加到PSS值中。如一个内存分页被两个进程共享,每个进程的PSS值会包括此内存分页大小的一半在内。
  • Dalvik Pss内存 = 私有内存Private Dirty + (共享内存Shared Dirty / 共享进程数)
  • TOTAL:上面全部条目的累加值,全局的展示了你的进程占用的内存情况。
  • ViewRootImpl:应用进程里的活动窗口视图个数,可以用来监测对话框或者其他窗口的内存泄露。
  • AppContexts及Activities:应用进程里Context和Activity的对象个数,可以用来监测Activity的内存泄露。

1.3 内存回收

在Android的高级系统版本里面针对Heap空间有一个Generational Heap Memory的模型,最近分配的对象会存放在Young Generation区域,当这个对象在这个区域停留的时间达到一定程度,它会被移动到Old Generation,最后累积一定时间再移动到Permanent Generation区域。系统会根据内存中不同的内存数据类型分别执行不同的gc操作。例如,刚分配到Young Generation区域的对象通常更容易被销毁回收,同时在Young Generation区域的gc操作速度会比Old Generation区域的gc操作速度更快。

2、内存测试方法

基于上面的理论学习,可以知道内存问题基本上就是三种:内存抖动、内存泄漏、内存溢出。我们测试内存的时候也主要关注这三个测试点。至于用什么方法进行测试,下面简单列举一下工具,基本上网上都有关于工具使用很详细的教程,在此不再详细述说。

2.1 检测内存抖动

内存抖动:大量的对象被创建又在短时间内马上被释放。
瞬间产生大量的对象会严重占用Young Generation的内存区域,当达到阀值,剩余空间不够的时候,也会触发GC。系统花费在GC上的时间越多,进行界面绘制或流音频处理的时间就越短。即使每次分配的对象占用了很少的内存,但是他们叠加在一起会增加Heap的压力,从而触发更多其他类型的GC。这个操作有可能会影响到帧率,并使得用户感知到性能问题。

  • Memory Monitor:查看整个app所占用的内存,以及发生GC的时刻,短时间内发生大量的GC操作是一个危险的信号(用于发现有没有内存泄漏和严重内存抖动)。
    例如:存在内存抖动.png

2.2 检测内存泄漏

内存泄露可以引发很多的问题:
1.程序卡顿,响应速度慢(内存占用高时JVM虚拟机会频繁触发GC)
2.莫名消失(当你的程序所占内存越大,它在后台的时候就越可能被干掉。反之内存占用越小,在后台存在的时间就越长)
3.直接崩溃(OutOfMemoryError)
内存泄漏无疑会严重影响用户体验,一些本应该废弃的资源和对象无法被释放,导致手机内存的浪费,app使用的卡顿,那么如何排查内存泄漏呢?

一个terminal指令:

adb shelldumpsys meminfo (package name)

这条指令是用来查询这个进程所占用的内存的具体详情的,通过这条指令可以看到当前app在手机中占用的具体的堆内存大小,view的数量activity的数量等等。

meminfo_内存泄漏.png

其中activity数目是非常关键的一个信息,可以帮助我们快速地检测出内存泄漏。我们可以反复地进入退出需要测试的目标activity,如果在反复进入退出之后,用terminal执行上面的语句查询当前的内存情况,如果发现activity数量一直在增长,如上图所示,APP退出后,再进入相当界面时Views和activity数量成倍地增长,则很大可能存在内存泄漏。

另外以下4个是用于定位的内存抖动和内存泄漏发生的具体位置·

  • Allocation Tracker

使用此工具来追踪内存的分配.
但是事实上,通过观察这个内存曲线的增长来或者是观察allocate tracker中的allocate data数值的增长来检测是否有内存泄漏问题,不太靠谱,因为往往内存泄漏发生了,但是GC仍然可以通过回收其他对象的方式腾出空间,导致这个数据的变化基本看不出来,甚至是减小的。

  • Heap Tool

查看当前内存快照,便于对比分析哪些对象有可能是泄漏了的。

  • Memory monitor :

如果是Dalvik内存泄漏,也可以使用Android Device Monitor dump出一份hprof文件(别忘了先手工Cause GC),生成hprof文件进行测试分析。用hprof分析工具,可以检测到泄漏的activities、分析出重复定义的字符串。

memory monitor.png

这里能够实时地显示应用程序占用的内存,很方便我们查看。总的来说,就是使用monitor memory功能监测app主进程占用的内存,触发GC操作,而后观察内存的占用情况,如果在使用的过程中内存不断增加,没有回落, 很有可能发生了内存泄漏,这时候就需要对生成的HPROF文件进行深入分析了。
hprof.png
使用HPROF文件分析工具标准步骤如下:
(1)、打开Captures窗口,双击你想要查看的HPROF文件,打开HPROF文件查看工具界面;
(2)、点击Android Studio主窗口右边栏上的Analyzer Tasks,默认HPROF文件分析工具会出现在HPROF文件查看工具的右边。Analyzer Tasks列表中选择你想分析的选项;
(3)、点击开始分析的按钮;
(4)、查看分析结果,点击结果中条目可在HPROF文件分析工具中查看详情。一般查看Retained Size占用最大的类,分析是否有内存泄漏。

名称 描述
Class name 类名
Total Count 该类的实例总数
Heap Count 所选择的堆中该类的实例的数量
Sizeof 单个实例所占空间大小(如果每个实例所占空间大小不一样则显示0)
Shallow Size 堆里所有实例大小总和(Heap Count * Sizeof)
Retained Size 当该对象被GC回收时,所释放掉的内存大小
Instance 具体的实例
Reference Tree 所选实例的引用,以及指向该引用的引用。
Depth GC根节点到所选实例的最短路径的深度
Shallow Size 所选实例的大小
Dominating Size 所选实例所支配的内存大小
  • MAT
    上述只是可以粗略的看出是不是有问题,而要知道问题出在哪里就需要借助MAT了。将生成的.hprof文件进行转换,然后使用MAT打开来分析应用的内存使用情况。通常在使用MAT打开hprof文件后,能够在首页看到Top Comnsumers和 component Report等功能,我们可以快速定位一些大块的内存消耗。
    TOP列表.png

    但我们在分析时会发现系统资源类占据了很大一部分内存,因此为去除这部分对分析的干扰,我们在使用AndroidSDK提供的hprof-conv转换时需要增加一个参数:
hporf- conv [-z] <infile><outfile> -z:exclude non-app heaps,such as Zygote

如果hprof文件是已经转换过的,则可以使用OQL:

//在数据中寻找应用的Application类对象,将对象地址转换为十进制后输入以下查询语句:
select * from instanceof java.langObject s where s.@objectAddress> 1107296256
//(后面那串数字应该是Application类对象的地址)

采用这两种方法后,再使用MAT来分析就可以比较容易发现自身代码的内存问题。

附:更多具体详细教程及其他排查经验请参考以下链接:
Android 性能优化之内存泄漏的检测与修复:http://blog.csdn.net/cleverGump/article/details/52013873
这一次,我优化了37%的内存:
http://wetest.qq.com/lab/view/147.html

  • 小结
  • MAT 是探索 Java 堆并发现问题和好帮手,能够迅速发现常见的图片和大数组等问题;
  • 内存碎片问题一般隐藏在对象的地址中;
  • 如需要测试非 Dalvik部分,有必要了解 Linux 的进程和内存原理、内存共享机制,熟悉常用命令行工具;
  • 内存分配的最小单位是页面,通常为4KB,这个限制会引发各种问题;

2.3 检测OOM

Android系统的每个进程都有一个最大内存限制(这个阈值可以是48M、24M、16M等,视机型而定),如果申请的内存资源超过这个限制,系统就会抛出OOM错误。
PS:可以通过adb命令查看阈值

adb shell getprop | grep dalvik.vm.heapgrowthlimit
[dalvik.vm.heapgrowthlimit]: [192m]

  • Android 2.x系统,当dalvik allocated + external allocated + 新分配的大小 >= dalvik heap 最大值时候就会发生OOM。其中bitmap是放于external中 。
  • Android 4.x系统,废除了external的计数器,类似bitmap的分配改到dalvik的java heap中申请,只要allocated + 新分配的内存 >= dalvik heap 最大值的时候就会发生OOM(art运行环境的统计规则还是和dalvik保持一致)

内存溢出是程序运行到某一阶段的最终结果,直接原因是剩余的内存不能满足内存的申请,但是再分析间接原因内存为什么没有了:

  • 内存泄漏的存在可能导致可用内存越来越少;
  • 内存申请的峰值超过了系统时间点剩余的内存;(例如:某手机单个进程可用最大内存为192M,目前分配内存80M,此时申请5M内存,但是当前时间点整个系统可用内存只有3M,此时没有超出单个进程可用最大内存,但是OOM也会发生)

2.4 常见内存测试场景

2.4.1 按各部分内存的用途设计场景

(1)比较操作前后或不同版本的内存变化
(2)显示多张图片的前台进程
(3)多个场景来回切换
(4)长时间运行进程的内存增长

2.4.1 根据比较结果,确定问题方向
android进程中内存分类.png

(1)Dalvik Heap内存

持续增长
内存泄露 -> LeakCanary / MAT

频繁GC,大幅度波动
大量的分配和释放 -> Allocation Tracker
比以前版本稳定增长
新功能及代码改动 -> Heap Dump / MAT
Heap Alloc不变,PSS增加
可能存在内存碎片 -> Heap Dump / MAT

(2)非Heap内存

Dalvik Other
类信息
载入Class数正相关
mmaps
可执行代码
常量

3、XX银行性能评测-内存测试结果分析

3.1 总览

此次质量开放平台-评测中心(http://fit-stg1.jryzt.com/Hyperion-server/html/index.html)的性能测试的采集的内存数据主要是针对场景页面的内存占用测试,内存占用数据获取原理是从memoryinfo中获取。

从内存占用对比看,行业竞品均值为351.3M,90分位约262.6M,75分位约339.5M,中位数约426.4M,25分位约605.7M。【榕商Bank】内存占用均值为246M,表现良好,打败了行业90%以上的竞品,请继续保持哦。

image.png

3.2 启动首页加载内存问题分析--存在内存抖动

这里选取了启动加载场景来进行内存问题的分析。

实际上从上面的总览数据分析看内存占用不同场景对比很难发现内存问题,但是同一个场景内存占用曲线图是可以发现问题的,如果曲线图有锯齿形的抖动且持续上升,基本上可能存在内存问题,如下图可得出首页存在内存抖动问题


首页加载内存抖动图1.png

首页加载内存抖动图2.png

另外还需要以日志辅助分析内存问题:从日志上看,1秒甚至几百ms内就有一次GC,而且是主动GC,说明在频繁申请内存,总阻塞耗时约5001 ms


GC.png
GC日志.png

注释:

  • GC Reason:GC触发原因
  • GC_CONCURRENT:当已分配内存达到某一值时,触发并发GC;
  • GC_FOR_MALLOC:当尝试在堆上分配内存不足时触发的GC;系统必须停止应用程序并回收内存;
    -GC_HPROF_DUMP_HEAP: 当需要创建HPROF文件来分析堆内存时触发的GC;
    -GC_EXPLICIT:当明确的调用GC时,例如调用System.gc()或者通过DDMS工具显式地告诉系统进行GC操作等;
  • Amount freed GC:回收的内存大小
  • Heap stats:堆上的空闲内存百分比 (已用内存)/(堆上总内存)
  • Pause time:这次GC操作导致应用程序暂停的时间。关于这个暂停的时间,在2.3之前GC操作是不能并发进行的,也就是系统正在进行GC,那么应用程序就只能阻塞住等待GC结束。而自2.3之后,GC操作改成了并发的方式进行,就是说GC的过程中不会影响到应用程序的正常运行,但是在GC操作的开始和结束的时候会短暂阻塞一段时间。

4、App端内存问题排查思路:

(1)Service停止使用时,是否被销毁

(2) 当界面变为不可见时,是否释放当前界面的资源

(3)内存变少时,是否有释放内存

(4) bitmap使用完之后是否被回收

(5)是否有大量第三方库的消耗

参考:

目录
相关文章
|
22天前
|
Kubernetes Cloud Native Java
云原生之旅:从容器到微服务的演进之路Java 内存管理:垃圾收集器与性能调优
【8月更文挑战第30天】在数字化时代的浪潮中,企业如何乘风破浪?云原生技术提供了一个强有力的桨。本文将带你从容器技术的基石出发,探索微服务架构的奥秘,最终实现在云端自由翱翔的梦想。我们将一起见证代码如何转化为业务的翅膀,让你的应用在云海中高飞。
|
8天前
|
Java 数据库 Android开发
一个Android App最少有几个线程?实现多线程的方式有哪些?
本文介绍了Android多线程编程的重要性及其实现方法,涵盖了基本概念、常见线程类型(如主线程、工作线程)以及多种多线程实现方式(如`Thread`、`HandlerThread`、`Executors`、Kotlin协程等)。通过合理的多线程管理,可大幅提升应用性能和用户体验。
25 15
一个Android App最少有几个线程?实现多线程的方式有哪些?
|
7天前
|
缓存 Java 测试技术
谷粒商城笔记+踩坑(11)——性能压测和调优,JMeter压力测试+jvisualvm监控性能+资源动静分离+修改堆内存
使用JMeter对项目各个接口进行压力测试,并对前端进行动静分离优化,优化三级分类查询接口的性能
谷粒商城笔记+踩坑(11)——性能压测和调优,JMeter压力测试+jvisualvm监控性能+资源动静分离+修改堆内存
|
10天前
|
Java 数据库 Android开发
一个Android App最少有几个线程?实现多线程的方式有哪些?
本文介绍了Android应用开发中的多线程编程,涵盖基本概念、常见实现方式及最佳实践。主要内容包括主线程与工作线程的作用、多线程的多种实现方法(如 `Thread`、`HandlerThread`、`Executors` 和 Kotlin 协程),以及如何避免内存泄漏和合理使用线程池。通过有效的多线程管理,可以显著提升应用性能和用户体验。
30 10
|
14天前
|
安全 Java API
【性能与安全的双重飞跃】JDK 22外部函数与内存API:JNI的继任者,引领Java新潮流!
【9月更文挑战第7天】JDK 22外部函数与内存API的发布,标志着Java在性能与安全性方面实现了双重飞跃。作为JNI的继任者,这一新特性不仅简化了Java与本地代码的交互过程,还提升了程序的性能和安全性。我们有理由相信,在外部函数与内存API的引领下,Java将开启一个全新的编程时代,为开发者们带来更加高效、更加安全的编程体验。让我们共同期待Java在未来的辉煌成就!
43 11
|
15天前
|
安全 Java API
【本地与Java无缝对接】JDK 22外部函数和内存API:JNI终结者,性能与安全双提升!
【9月更文挑战第6天】JDK 22的外部函数和内存API无疑是Java编程语言发展史上的一个重要里程碑。它不仅解决了JNI的诸多局限和挑战,还为Java与本地代码的互操作提供了更加高效、安全和简洁的解决方案。随着FFM API的逐渐成熟和完善,我们有理由相信,Java将在更多领域展现出其强大的生命力和竞争力。让我们共同期待Java编程新纪元的到来!
38 11
|
18天前
|
安全
【Azure App Service】App service无法使用的情况分析
App Service集成子网后,如果子网网段中的剩余IP地址非常少的情况下,会在App Service实例升级时( 先加入新实例,然后在移除老实例 )。新加入的实例不能被分配到正确的内网IP地址,无法成功的访问内网资源。 解决方法就是为App Service增加子网地址, 最少需要/26 子网网段地址。
|
24天前
|
API Android开发
Android P 性能优化:创建APP进程白名单,杀死白名单之外的进程
本文介绍了在Android P系统中通过创建应用进程白名单并杀死白名单之外的进程来优化性能的方法,包括设置权限、获取运行中的APP列表、配置白名单以及在应用启动时杀死非白名单进程的代码实现。
42 1
|
7天前
|
监控 算法 数据可视化
深入解析Android应用开发中的高效内存管理策略在移动应用开发领域,Android平台因其开放性和灵活性备受开发者青睐。然而,随之而来的是内存管理的复杂性,这对开发者提出了更高的要求。高效的内存管理不仅能够提升应用的性能,还能有效避免因内存泄漏导致的应用崩溃。本文将探讨Android应用开发中的内存管理问题,并提供一系列实用的优化策略,帮助开发者打造更稳定、更高效的应用。
在Android开发中,内存管理是一个绕不开的话题。良好的内存管理机制不仅可以提高应用的运行效率,还能有效预防内存泄漏和过度消耗,从而延长电池寿命并提升用户体验。本文从Android内存管理的基本原理出发,详细讨论了几种常见的内存管理技巧,包括内存泄漏的检测与修复、内存分配与回收的优化方法,以及如何通过合理的编程习惯减少内存开销。通过对这些内容的阐述,旨在为Android开发者提供一套系统化的内存优化指南,助力开发出更加流畅稳定的应用。
19 0
|
20天前
|
Android开发 iOS开发 C#
Xamarin:用C#打造跨平台移动应用的终极利器——从零开始构建你的第一个iOS与Android通用App,体验前所未有的高效与便捷开发之旅
【8月更文挑战第31天】Xamarin 是一个强大的框架,允许开发者使用单一的 C# 代码库构建高性能的原生移动应用,支持 iOS、Android 和 Windows 平台。作为微软的一部分,Xamarin 充分利用了 .NET 框架的强大功能,提供了丰富的 API 和工具集,简化了跨平台移动应用开发。本文通过一个简单的示例应用介绍了如何使用 Xamarin.Forms 快速创建跨平台应用,包括设置开发环境、定义用户界面和实现按钮点击事件处理逻辑。这个示例展示了 Xamarin.Forms 的基本功能,帮助开发者提高开发效率并实现一致的用户体验。
47 0