Android 性能优化:内存优化(理论篇)

news2024/12/29 5:08:05

内存作为App程序运行最重要的资源之一,需要运行过程中做到合理的资源分配与回收,不合理的内存占用轻则使得用户应用程序运行卡顿、ANR、黑屏,重则导致用户应用程序发生 OOM(out of memory)崩溃。喜马直播随着近些年的业务迭代功能不断完善玩法丰富,需要在各种机器资源上保持优秀的流畅性和稳定性,内存优化是必须要重视的环节。

从目前的内存水位和APM建设入手,梳理统计规则和项目真实内存水位,在代码和业务多方面熟悉和归因,搭配工具建设和使用为抓手,最后通过内存专项治理将应用的 OOM优化到合理水位。

限于篇幅和功能性,文章总共分为理论篇实践篇两个部分。

写这篇文章动机,主要是工作中进行内存优化专项,便于将以往琐碎的内存知识和优化思路整合,所以有了这篇文章,感谢前人的经验。谨以此篇文章记录工作思路及基础知识,温故知新。

思维导图

在这里插入图片描述

内存基础知识

Android 最新系统都运行在ART虚拟机上,基于 Linux 内核实现,Linux的内存管理哲学是:Free memory is wasted memory。即内存没有得到充分利用就是在浪费内存。因此 Linux 希望尽可能多的使用内存,较少磁盘 IO 。Android 系统继承了 Linux 的优点,同样是尽最大限度使用原则。

与Linux不同的是 Android 侧重于可能多的缓存进程以提高应用启动和切换速度。即,Android系统会在内存中尽量的长时间的保持应用进程,直到系统分配内存不足时才会去根据进程优先级、内存代销等条件回收进程。这些保留在内存中的进程通常不会影响系统整体的运行速度,反而会在用户再次激活这些进程时,加快进程的启动速度。

在内存管理上,JVM拥有垃圾内存回收的机制,自身会在虚拟机层面自动分配和释放内存,因此不需要像使用C/C++一样在代码中分配和释放某一块内存。Android系统的内存管理原理基础就是JVM,通过new关键字来为对象分配内存,内存的释放由GC来回收。并且Android系统在内存管理上有一个 Generational Heap Memory模型,当内存达到某一个阈值时,系统会根据不同的规则自动释放可以释放的内存。即便有了内存管理机制,但是,如果不合理地使用内存,也会造成一系列的性能问题,比如 内存泄漏、内存抖动、短时间内分配大量的内存对象,接下来详细记录下Android内存管理模式及知识点。

Jvm内存分配模型

JVM 将整个内存划分为了几块:

  1. 方法区:存储类信息、常量、静态变量等。(所有线程共享)
  2. 虚拟机栈:存储局部变量表、操作数栈等。
  3. 本地方法栈:不同于虚拟机栈为 Java 方法服务、它是为 Native 方法服务的。
  4. 堆:内存最大的区域,每一个对象实际分配内存都是在堆上进行分配的,,而在虚拟机栈中分配的只是引用,这些引用会指向堆中真正存储的对象。此外,堆也是垃圾回收器(GC)所主要作用的区域,并且,内存泄漏也都是发生在这个区域。(所有线程共享)
  5. 程序计数器:存储当前线程执行目标方法执行到了第几行。

Android内存分配

Android Runtime有两种虚拟机,Dalvik 和 ART,实际上就是一块匿名共享内存。Android虚拟机仅仅只是把它封装成一个 mSpace由底层C库来管理,并且仍然使用libc提供的函数malloc和free来分配和释放内存

大多数静态数据会被映射到一个共享的进程中。常见的静态数据包括Dalvik Code、app resources、so文件等等。Android通过显示分配共享内存区域(如Ashmem或者Gralloc)来实现动态RAM区域能够在不同进程之间共享的机制。例如,Window Surface在App和Screen Compositor之间使用共享的内存,Cursor Buffers在Content Provider和Clients之间共享内存。下面简单总结下我理解的堆结构:

Dalvik

Linear Alloc 、 Zygote Space 和 Alloc Space

ART

(重点汇总下)

  • Zygote Space: 包含由 Zygote 进程预加载并在所有应用程序之间共享的对象。

  • Allocation Space:用于存储应用程序在运行时创建的对象,主要的GC工作区域,为每个进程独自使用。

  • Non-Moving Space:存储不需要移动的对象,如 ART 内部数据结构,和Dalvik中的Linear Alloc类似。

  • Large Object Space :用于存储大对象(通常大于 12KB)使用单独的分配策略和GC机制。

  • Region Space :用于新生代对象的分配和管理,是离散地址的集合,使用更高效的垃圾回收机制。

  • Image Space:包含预编译的系统类和应用程序类,从预生成的映像文件中内存映射而来,在Zygote和其他应用程序进程之间共享,不参与GC。

Android系统的第一个虚拟机由Zygote进程创建并且只有一个Zygote Space。但是当Zygote进程在fork第一个应用程序进程之前,会将已经使用的那部分堆内存划分为一部分,还没有使用的堆内存划分为另一部分,也就是Allocation Space。但无论是应用程序进程,还是Zygote进程,当他们需要分配对象时,都是在各自的Allocation Space堆上进行

单进程内存上限

Android 系统的 Java虚拟机会对单个进程使用的最大内存做限制,该属性值定义在/system/build.prop文件中,厂商一般会根据设备自身内存大小来设定这个值,不同的设备分配给APP的最大可用内存是不相同的。进程启动时,系统会先为APP分配一定的内存空间,当分配的内存快要耗尽时,系统会再次为App 分配更多的内存,但是每个APP都有内存使用上限,一旦进程分配了最大可用内存后,内存依然不足则会直接抛出OOM异常,终止程序的运行。可以通过调用 getMemoryClass() 向系统查询此数值。此方法返回一个整数,表示应用堆的可用兆字节数。

内存垃圾回收

当进程使用内存达到设定的阈值时,就会触发虚拟机的GC机制,虽然新一代的ART对于通过分代垃圾回收和高效的内存分配机制,ART 能够更加高效地使用内存,减少内存碎片和内存泄漏等方面的优化,但是GC的时候,还是会导致 STW (Stop The World),所以为了提升程序性能,有必要进行内存优化。下边列举一下Java的内存回收算法。

标记清除算法

标记清除算法是一种垃圾回收算法,用于管理动态分配的内存。它的主要思想是,遍历整个堆,标记所有被引用的对象,当某个对象不再被程序所引用时,它就可以被认为是“垃圾”,并被回收以便后续的内存分配。 优缺点:自动管理动态分配的内存,但是清除后可能导致大量的内存碎片,降低堆利用率。

复制算法

复制算法是一种将内存分为两个区域的算法,其中一个区域用于存储活动对象,另一个区域用于存储不再使用的对象。 优缺点:运行效率高,但是浪费一半空间,代价较高。

标记整理算法

标记整理算法是标记清除算法和复制算法的结合,其工作原理是先标记出不再使用的对象,再整理内存使得活动对象的内存分配连续,优缺点:解决了标记清除算法导致的内存碎片问题,但是也产生了一些问题,由于进行了两次扫描,增加了时间开销。 相较其他垃圾回收算法,速度较慢,不适合新生代场景,并且标记整理算法的效率也受内存使用情况影响,效率不稳定等问题。

分代收集算法

分代回收算法是一种将内存分为几个代的算法,并对每个代进行不同的回收策略,这里就需要借一张图了,一图胜千言

image.png

新创建的对象 , 放在年轻代内存块中 , 开始时放在 Eden 区域 , 当 Eden 区域存满后 , 会将存活的对象转移到 From 区域 和 To 区域,对象每经过一次 GC 垃圾回收 , 其年龄就会加 1 ; 当年龄到达虚拟机设置的阈值之后 , 就会被放入老年代内存块中 ,持久代内存区域,主要存放着类加载器加载的Class和常量池等对象。

image.png

  • 年轻代内存区域的垃圾回收器 : Minor GC (Serial,ParNew 和 Parallel Scavenge)

  • 老年代内存区域的垃圾回收器 : Major GC (CMS ,Serial Old和 Parallel Old)

  • 整个内存区域的垃圾回收器 : Full GC

持久代内存区域的内存不回收 ;年轻代内存区域与老年代内存区域的垃圾回收机制不同的。

Serial ParNew都是 使用的复制算法,主要在年轻代中收集要回收的内存,但是Serial是单线程串行,而ParNew则是多线程运行。

CMS Concurrent Mark Sweep ,并发标记清除收集器,采用标记清除算法,gc过程,用户线程仅做最短停顿,具体流程如下:

  • 初始标记 : 标记与 GC Roots 有引用链的对象 ; 该操作速度快 , 该步骤需要暂停用户线程。

  • 并发标记 : GC Roots 追踪,从初始标记结果集合中标记出存活对象,不能保证所有的存活对象都被标记 ; 该步骤与应用程序并发执行。

  • 重新标记 : 上一步并发标记 GC 线程与用户程序并发期间的标记有部分变化 , 修正这部分标记信息之后暂停用户线程 开始标记,该暂停操作要比初始标记步骤暂停时间长。

  • 并发清除 : 回收所有 GC Roots 不可达对象

CMS的优点是,并发收集,低停顿。有一个明显缺点就是因为采用了“标记-清除”算法,最后会出现大量碎片,有可能会出现在某一个时刻,当有大对象生成,不得不进行一次Full GC来解决这个问题。为了解决该问题CMS有一个参数-XX:UseCmsCompactAtFullCollection来解决因为空间不足进行Full GC。这个参数默认开启,用于在CMS收集器顶不住要进行Full GC是开启内存碎片合并整理的过程,内存整理过程是无法并发的,因此就会耗时。同时还有一个参数是-XX:CMSFullGCsBeforeCompaction,这个参数是用于执行多次不压缩的GC后,跟着来一次压缩的(默认是0,表示每次进入Full GC都进行碎片整理)。

G1收集器

Garbage-First收集器:并行和并发,分代收集。 G1和其他收集器不同的是,其他收集器收集范围都是整个新生代和老年代,而G1不再是这样。使用G1收集器的时候,Java堆分为多个大小相等的区域(Region),虽然也保留了新生代和老年代的概念,但是不再是物理隔离了,他们都是一部分Region的集合(这些Region不一定是连续的)。G1保留了Eden和Survivor的比例也是8:1:1。

ZGC

JDK11引入的ZGC收集器,在物理和逻辑上已经没有新/老年代的概念了,会分为一个个page,当进行GC操作时候会对page进行压缩,因此没有碎片问题,只能在64位linux上使用。

  • 可以达到10ms以内的停顿要求
  • 支持TB级别的内存
  • 堆内存变大后停顿时间还是在10ms以内

Java 引用类型

既然上边列举了主要的回收算法,这里简单带过一下引用类型,也是内存优化过程中,经常使用到的知识点:

  • 强引用 :强引用是 Java 中最常见的引用类型,当对象具有强引用时,它永远不会被垃圾回收。只有在程序结束或者手动将对象设置为 null 时,才会释放强引用,像常用的 new 方式。
  • 软引用 :当 Java 堆内存不足时,软引用可能会被回收,以腾出内存空间。如果内存充足,则软引用可以继续存在,使用SoftReference创建,gc的时候可能并不会释放软引用持有对象。
  • 弱引用 :在垃圾回收器线程扫描它所管辖的内存区域的过程中,发现具有弱引用的对象,不管当前内存空间是否足够,都会回收它的内存。
  • 虚引用 : 只能用于跟踪即将对被引用对象进行的收集。虚拟机必须与ReferenceQueue类联合使用。因为它能够充当通知机制。

内存优化的必要性

经过上述基础知识的分析,我们认识到内存在手机侧的宝贵和重要性,安卓系统对每个应用程序都有一定的内存限制,当应用程序的内存超过了上限,就会出现 OOM,造成App的异常退出。

因此,要改善系统的运行效率、改善用户体验、降低系统资源占用、延长电池寿命、降低系统故障的危险。Android通过内存优化,可以减少系统内存使用 提高应用后台运行存活率,让系统更加流畅,秒开率更高,减少系统GC次数,减少页面卡顿率,降低内存泄漏率,从而提高App的整体体验和功耗优化。

内存优化思路及SOP

在这里插入图片描述

数据收集及水位分析

关于性能或功耗方面的优化问题,都要先做到摸清大盘数据,特别是我这种新的公司,新的APM系统的,一定要摸清APM上报原理和逻辑,并且输出自己的文档,好记性不如烂笔头。

查看内存泄露、内存溢出和线程超标等方面的数据后,动手改代码前要确定好优化后的目标水位是多少,定好里程碑,及时同步领导,避免大方向错误。

借助工具排查问题

在线上大盘水位摸清之后,比如我们是基于Koom进行了魔改,就可以将上报的内存泄露问题统一汇总并解决,但是内存的水位偏高和内存抖动问题,就需要借助本地检测工具进行排查。下篇具体汇总下,通过工具查看问题。 比如图片使用方面,可以使用 Hook native bitmap (新版本都是用Native内存,暂不考虑 8.0 之前的版本情况了)检测图片在退出页面后,是否清理完成。

问题优化

针对不同类型问题需要使用不同的方式,比如内存抖动问题,存在一些大对象或者过多小对象的情况,可能存在频繁创建对象等问题。如果存在内存泄露等问题,则需要根据堆栈调用链,排查那些代码存在纰漏。针对问题优化后,做好兜底策略,并后续关注新版本是否有新的上报。

长效治理策略

问题如果解决后,可以将常见的内存泄露问题进行汇总,在排查问题过程中,使用到的工具及操作流程进行整理,汇总成内存优化SOP,可以在组内进行技术分享,提高团队的内存优化意识。

关于长期卡口,可以尝试在Git Hook的办法,针对提交的代码进行监控,如果出现内存泄露的写法阻止代码提交,并展示警告信息。

参考文章

Android性能优化之内存优化 - JsonChao

分代收集算法

深入探索 Android 内存优化(炼狱级别-上)

深入探索 Android 内存优化(炼狱级别-下)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2247632.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【AI技术赋能有限元分析应用实践】pycharm终端与界面设置导入Abaqus2024自带python开发环境

目录 一、具体说明1. **如何在 Windows 环境中执行 Abaqus Python 脚本**2. **如何在 PyCharm 中配置并激活 Abaqus Python 环境**3. **创建 Windows 批处理脚本自动执行 Abaqus Python 脚本**总结二、方法1:通过下面输出获取安装路径导入pycharm方法2:终端脚本执行批处理脚本…

arm学习总结

AHB总线连接的都是这些需要高速处理的内存、内核啥的 APB连接的都是些外设 GPIO并不需要高速处理的

【redis 】string类型详解

string类型详解 一、string类型的概念二、string类型的常用指令2.1 SET2.2 GET2.3 MSET2.4 MGET2.5 SETNX2.6 INCR2.7 INCRBY2.8 DECR2.9 DECRBY2.10 INCRBYFLOAT2.11 APPEND2.12 GETRANGE2.13 SETRANGE2.14 STRLEN 三、string类型的命令小结四、string类型的内部编码五、strin…

Mysql案例之COALESCE函数使用详解

hello,大家好,我是灰小猿!最近在做一个三表关联查询的场景处理时,遇到了一个比较有用的MySQL函数,在这里记录一下,大概场景如下: 需求场景 场景:有一张object_rel表,表中…

C语言菜鸟入门·关键字·int的用法

目录 1. int关键字 1.1 取值范围 1.2 符号类型 1.3 运算 1.3.1 加法运算() 1.3.2 减法运算(-) 1.3.3 乘法运算(*) 1.3.4 除法运算(/) 1.3.5 取余运算(%) 1.3.6 自增()与自减(--) 1.3.7 位运算 2. 更多关键字 1. int关键字 int 是一个关键字&#xff0…

D74【 python 接口自动化学习】- python 基础之HTTP

day74 http基础定义 学习日期:20241120 学习目标:http定义及实战 -- http基础介绍 学习笔记: HTTP定义 HTTP 是一个协议(服务器传输超文本到浏览器的传送协议),是基于 TCP/IP 通信协议来传递数据&…

基于FPGA(现场可编程门阵列)的SD NAND图片显示系统是一个复杂的项目,它涉及硬件设计、FPGA编程、SD卡接口、NAND闪存控制以及图像显示等多个方面

文章目录 0、前言 1、目标 2、图片的预处理 3、SD NAND的预处理 4、FPGA实现 4.1、详细设计 4.2、仿真 4.3、实验结果 前言 在上一篇文章《基于FPGA的SD卡的数据读写实现(SD NAND FLASH)》中,我们了解到了SD NAND Flash的相关知识&am…

【计算机网络】网段划分

一、为什么有网段划分 IP地址 网络号(目标网络) 主机号(目标主机) 网络号: 保证相互连接的两个网段具有不同的标识 主机号: 同一网段内,主机之间具有相同的网络号,但是必须有不同的主机号 互联网中的每一台主机,都要隶属于某一个子网 -&…

Java-反序列化

序列化与反序列化 简单demo: import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable;public class serialize implements Serializable{private…

webkit浏览器内核编译(2024年11月份版本)

webkit浏览器内核编译 本文详细介绍了如何安装和配置Webkit的编译环境和工具的安装,以及在Windows上编译和运行WebKit浏览器引擎的过程,包括安装依赖、设置环境变量、生成解决方案并最终运行附带的MiniBrowser示例。 一、WebKit简介 WebKit 是一个开源的…

论文阅读--Evidence for the utility of quantum computing before fault tolerance

量子计算有望在某些问题上提供比传统计算更快的速度。然而,实现其全部潜力的最大障碍是这些系统固有的噪声。这一挑战被广泛接受的解决方案是实现容错量子电路,而这超出了当前处理器的能力范围。我们在此报告了在嘈杂的127 量子比特处理器上进行的实验&a…

构建高效在线教育:SpringBoot课程管理系统

1系统概述 1.1 研究背景 随着计算机技术的发展以及计算机网络的逐渐普及,互联网成为人们查找信息的重要场所,二十一世纪是信息的时代,所以信息的管理显得特别重要。因此,使用计算机来管理在线课程管理系统的相关信息成为必然。开发…

Linux 下的IO模型

一:四种IO模 1.1:阻塞式IO(最简单,最常用,效率最低) 阻塞I/O 模式是最普遍使用的I/O 模式,大部分程序使用的都是阻塞模式的I/O 。 缺省情况下(及系统默认状态)&#xf…

Linux-Nginx反向代理

文章目录 反向代理负载均衡 🏡作者主页:点击! 🤖Linux专栏:点击! ⏰️创作时间:2024年11月24日10点32分 反向代理 虚拟主机 1 为虚拟主机 3 提供代理服务 vi /etc/nginx/conf.d/vhost.confser…

DataGrip 连接 Redis、TongRDS

连接 Redis 或 TongRDS 有些旧版本 没有 redis 驱动用不了 1)选择驱动 2)添加连接信息 3)测试连接 4)保存连接 5)使用案例

《数据结构》学习系列——图(中)

系列文章目录 目录 图的遍历深度优先遍历递归算法堆栈算法 广度优先搜索 拓扑排序定义定理算法思想伪代码 关键路径基本概念关键活动有关量数学公式伪代码时间复杂性 图的遍历 从给定连通图的某一顶点出发,沿着一些边访问遍图中所有的顶点,且使每个顶点…

CodeCache使用率告警分析

CodeCache 是 JVM 用于存储已编译的本地代码(即 JIT 编译生成的代码)的内存区域。如果 CodeCache 使用率持续较高,特别是大于 80%,可能会导致性能问题甚至应用运行异常。以下是详细分析: 一、CodeCache 使用率告警的意…

CSS:怎么把网站都变成灰色

当大家看到全站的内容都变成了灰色,包括按钮、图片等等。这时候我们可能会好奇这是怎么做到的呢? 有人会以为所有的内容都统一换了一个 CSS 样式,图片也全换成灰色的了,按钮等样式也统一换成了灰色样式。但你想想这个成本也太高了…

JS的DOM操作和事件监听综合练习 (具备三种功能的轮播图案例)

下面是是对dom操作的一个综合练习 下面代码是html的基本骨架&#xff08;没有任何的功能&#xff09;&#xff1a; <!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <meta name"viewport" c…

GitHub 开源项目 Puter :云端互联操作系统

每天面对着各种云盘和在线应用&#xff0c;我们常常会遇到这样的困扰。 文件分散在不同平台很难统一管理&#xff0c;付费订阅的软件越来越多&#xff0c;更不用说那些烦人的存储空间限制了。 最近在 GitHub 上发现的一个开源项目 Puter 彻底改变了我的在线办公方式。 让人惊…