Android内存优化内存抖动的解决实战

问题背景

假设我们有一个应用,它的功能是在一个TextView上显示一个计数器,每隔一秒钟就更新一次计数器的值。为了实现这个功能,我们使用了一个Handler来发送空消息,并在接收到消息时更新计数器的值,并再次发送空消息,形成一个循环。同时,为了模拟一些复杂的业务逻辑,我们在循环中创建了大量的数组对象。以下是我们的代码:

public class MainActivity extends AppCompatActivity {// 定义一个TextView来显示计数器的值private TextView textView;// 定义一个计数器变量private int counter = 0;// 定义一个Handler对象private Handler handler = new Handler() {@Overridepublic void handleMessage(@NonNull Message msg) {super.handleMessage(msg);// 更新计数器的值counter++;// 在TextView上显示计数器的值textView.setText(String.valueOf(counter));// 模拟一些复杂的业务逻辑,创建大量的数组对象for (int i = 0; i < 1000; i++) {int[] array = new int[1000];array[0] = i;}// 再次发送空消息,形成一个循环handler.sendEmptyMessage(0);}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// 初始化TextViewtextView = findViewById(R.id.textView);// 发送空消息,启动循环handler.sendEmptyMessage(0);}
}

这段代码看起来没有什么问题,但是当我们运行这个应用时,我们会发现应用的内存使用情况非常不稳定,内存曲线呈现出明显的锯齿状,GC事件也非常频繁。这就是典型的内存抖动的现象。

问题定位

要定位这个问题,我们可以使用Memory Profiler来观察应用的内存使用情况。Memory Profiler是Android Studio中的一个工具,它可以实时显示应用的内存使用情况,包括内存分配、回收、泄漏等。通过Memory Profiler,我们可以发现应用存在明显的内存波动和GC频率。

以下是一个使用Memory Profiler观察应用内存使用情况的示例图:

从示例图中可以看出:

  • Memory Usage:这个图表显示了应用在不同时间点的内存使用量,以及不同类型的内存(如Java Heap、Native Heap、Graphics等)。从图表中可以看出,应用的Java Heap内存使用量呈现出明显的波动性,上下起伏,形成锯齿状。这说明应用存在大量短期存在的对象,导致内存分配和回收不平衡。
  • GC Events:这个图表显示了应用在不同时间点发生的GC事件,以及GC事件的类型(如GC Alloc、GC Concurrent等)。从图表中可以看出,应用发生了非常频繁的GC事件,几乎每隔一秒钟就会发生一次GC事件。这说明应用的GC压力非常大,GC需要消耗更多的CPU资源,并且影响应用线程的执行。

从这里重新输出,我会继续为你介绍内存抖动的解决实战。接下来,我将从以下几个方面给你展示如何使用Memory Profiler和代码排查来分析和解决一个具体的内存抖动问题:

问题分析

要分析这个问题,我们可以使用Memory Profiler来捕获堆转储,并根据对象分配跟踪来找出导致内存抖动的代码位置和原因。堆转储是一种保存应用在某个时间点的内存快照的文件,它可以用来查看应用中存在的所有对象,以及它们的类型、大小、数量等。对象分配跟踪是一种记录应用在一段时间内创建的所有对象,以及它们的类型、大小、数量、调用栈等的功能。通过堆转储和对象分配跟踪,我们可以发现应用中产生内存抖动的代码位置和原因。

以下是使用Memory Profiler捕获堆转储和对象分配跟踪的示例图:

从示例图中可以看出:

  • Heap Dump:这个功能可以让我们捕获应用在某个时间点的堆转储文件,并查看其中的内容。从堆转储文件中,我们可以看到应用中存在的所有类和实例,以及它们的类型、大小、数量等。通过对比不同时间点的堆转储文件,我们可以发现哪些类和实例是短期存在的,导致内存波动。
  • Allocation Tracking:这个功能可以让我们记录应用在一段时间内创建的所有对象,并查看其中的内容。从对象分配跟踪中,我们可以看到应用中创建的所有对象,以及它们的类型、大小、数量、调用栈等。通过分析对象分配跟踪,我们可以找出哪些代码位置是创建了大量对象,导致GC频繁。

使用这两个功能,我们可以定位到导致内存抖动的代码位置和原因。经过分析,我们发现:

  • 在堆转储文件中,我们发现有大量的int[]数组对象存在于Java Heap中,它们占用了大部分的内存空间,并且在不同时间点的堆转储文件中,它们的数量和大小都有明显的变化。这说明这些数组对象是短期存在的,导致内存波动。
  • 在对象分配跟踪中,我们发现有大量的int[]数组对象被创建,它们的类型、大小、数量都是一致的。通过查看它们的调用栈,我们发现它们都是在MainActivity的handleMessage方法中创建的。这说明这个方法是创建了大量数组对象,导致GC频繁。 接下来,我将从以下几个方面给你展示如何使用Memory Profiler和代码排查来分析和解决一个具体的内存抖动问题:

问题解决

要解决这个问题,我们可以修改代码逻辑,避免在循环中创建大量数组,或者使用对象池来复用数组对象,从而减少内存分配和回收。以下是我们的优化方案:

  • 避免在循环中创建大量数组:如果我们不需要在循环中创建大量数组,我们可以将数组的创建放在循环之外,或者使用静态变量或者成员变量来保存数组。这样就可以避免每次循环都创建一个新的数组对象,减少内存分配和回收。例如:
public class MainActivity extends AppCompatActivity {// 定义一个TextView来显示计数器的值private TextView textView;// 定义一个计数器变量private int counter = 0;// 定义一个Handler对象private Handler handler = new Handler() {@Overridepublic void handleMessage(@NonNull Message msg) {super.handleMessage(msg);// 更新计数器的值counter++;// 在TextView上显示计数器的值textView.setText(String.valueOf(counter));// 模拟一些复杂的业务逻辑,使用一个静态变量来保存数组对象,避免在循环中创建大量数组对象for (int i = 0; i < 1000; i++) {array[0] = i;}// 再次发送空消息,形成一个循环handler.sendEmptyMessage(0);}};// 定义一个静态变量,用来保存数组对象private static int[] array = new int[1000];@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// 初始化TextViewtextView = findViewById(R.id.textView);// 发送空消息,启动循环handler.sendEmptyMessage(0);}
}
  • 使用对象池来复用数组对象:如果我们需要在循环中创建大量数组,我们可以使用对象池来管理和复用数组对象,而不是每次都创建和销毁数组对象。当需要一个数组时,可以从对象池中获取一个空闲的数组,使用完毕后,可以将数组归还到对象池中,等待下次使用。这样就可以避免频繁的内存分配和回收,减少GC的压力。例如:
public class MainActivity extends AppCompatActivity {// 定义一个TextView来显示计数器的值private TextView textView;// 定义一个计数器变量private int counter = 0;// 定义一个Handler对象private Handler handler = new Handler() {@Overridepublic void handleMessage(@NonNull Message msg) {super.handleMessage(msg);// 更新计数器的值counter++;// 在TextView上显示计数器的值textView.setText(String.valueOf(counter));// 模拟一些复杂的业务逻辑,使用一个对象池来管理和复用数组对象,避免在循环中创建大量数组对象for (int i = 0; i < 1000; i++) {// 从对象池中获取一个空闲的数组对象int[] array = arrayPool.getArray();array[0] = i;// 将数组对象归还到对象池中arrayPool.returnArray(array);}// 再次发送空消息,形成一个循环handler.sendEmptyMessage(0);}};// 定义一个对象池,用来管理和复用数组对象private ArrayPool arrayPool;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// 初始化TextViewtextView = findViewById(R.id.textView);// 初始化对象池arrayPool = new ArrayPool(1000, 1000);// 发送空消息,启动循环handler.sendEmptyMessage(0);}
}

为了帮助到大家更好的全面清晰的掌握好性能优化,准备了相关的核心笔记(还含底层逻辑):https://qr18.cn/FVlo89

性能优化核心笔记:https://qr18.cn/FVlo89

启动优化

内存优化

UI优化

网络优化

Bitmap优化与图片压缩优化https://qr18.cn/FVlo89

多线程并发优化与数据传输效率优化

体积包优化

《Android 性能监控框架》:https://qr18.cn/FVlo89

《Android Framework学习手册》:https://qr18.cn/AQpN4J

  1. 开机Init 进程
  2. 开机启动 Zygote 进程
  3. 开机启动 SystemServer 进程
  4. Binder 驱动
  5. AMS 的启动过程
  6. PMS 的启动过程
  7. Launcher 的启动过程
  8. Android 四大组件
  9. Android 系统服务 - Input 事件的分发过程
  10. Android 底层渲染 - 屏幕刷新机制源码分析
  11. Android 源码分析实战

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

利用爬虫技术自动化采集汽车之家的车型参数数据

导语 汽车之家是一个专业的汽车网站&#xff0c;提供了丰富的汽车信息&#xff0c;包括车型参数、图片、视频、评测、报价等。如果我们想要获取这些信息&#xff0c;我们可以通过浏览器手动访问网站&#xff0c;或者利用爬虫技术自动化采集数据。本文将介绍如何使用Python编写…

Android 实现椭圆形中心发散渐变绘制

1. 前言 工作中有一个需求,需要实现一个椭圆形的中心发散渐变效果,并且可以自由指定椭圆比例,旋转角度等。 Android中实现椭圆形的绘制很简单,只需要调用canvas.drawOval()就可以了,圆形的中心发散渐变可以使用RadialGradient,但是椭圆形的中心发散渐变效果Android自带的…

浅谈Deep Learning 与 Machine Learning 与Artificial Intelligence

文章目录 三者的联系与区别 三者的联系与区别 “Deep Learning is a kind of Machine Learning, and Machine Learning is a kind of Artificial Intelligence.” 人工智能&#xff08;AI&#xff09;&#xff0c;机器学习&#xff08;Machine Learning&#xff0c;简称ML&am…

Linux环境变量配置说明(配置jdk为例-摘录自尚硅谷技术文档)

配置环境变量的不同方法 Linux的环境变量可在多个文件中配置,如/etc/profile&#xff0c;/etc/profile.d/.sh&#xff0c;~/.bashrc&#xff0c;~/.bash_profile等&#xff0c;下面说明上述几个文件之间的关系和区别。 bash的运行模式可分为login shell和non-login shell。 例…

Unity3D C# 反射与特性的配合使用

需求分析 情况&#xff1a; 假如我们是一个动物园的管理员&#xff0c;我们需要统计园内的所有动物和动物的行为。 举例&#xff1a; 现在园区内有猫、狗和鸡。猫对应的行为是喵喵喵和卖萌&#xff0c;狗对应狗吠和干饭&#xff0c;鸡对应篮球和打鸣那么这时候我要统计这些&a…

R语言风险价值:ARIMA,GARCH,Delta-normal法滚动估计VaR(Value at Risk)和回测分析股票数据...

全文链接&#xff1a;http://tecdat.cn/?p24492 此分析的目的是构建一个过程&#xff0c;以在给定时变波动性的情况下正确估计风险价值。风险价值被广泛用于衡量金融机构的市场风险。我们的时间序列数据包括 1258 天的股票收益&#xff08;点击文末“阅读原文”获取完整代码数…

Linux chmod命令——修改权限信息

我们可以使用chmod命令&#xff0c;修改文件、文件夹的权限信息。注意&#xff0c;只有文件、文件夹的所属用户或root用户可以修改。 chmod [-R] 权限 文件或文件夹 -R&#xff0c;对文件夹内的全部内容应用同样的操作 例如&#xff1a; chmod urwx,grx,ox hello.txt &…

基于微信小程序的项目申报管理系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言用户微信端的主要功能有&#xff1a;管理员的主要功能有&#xff1a;具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉…

flarum 论坛 User Statistics插件修改

此插件在中国使用日期不是很理想&#xff0c;于是决定修改代码 下面是插件信息&#xff1a; User Statistics A Flarum extension. Add some user statistics in flarum posts, this extension require clarkwinkelmann/flarum-ext-likes-received and will be installed au…

【postgresql】替换 mysql 中的ifnull()

数据库由mysql 迁移到postgresql&#xff0c;程序在执行查询时候报错。 HINT: No function matches the given name and argument types. You might need to add explicit type casts. CONTEXT: referenced column: ifnull 具体SQL: SELECT ifnull(phone,) FROM c_user p…

Rust中的结构体

专栏简介&#xff1a;本专栏作为Rust语言的入门级的文章&#xff0c;目的是为了分享关于Rust语言的编程技巧和知识。对于Rust语言&#xff0c;虽然历史没有C、和python历史悠远&#xff0c;但是它的优点可以说是非常的多&#xff0c;既继承了C运行速度&#xff0c;还拥有了Java…

钾和钠含量

声明 本文是学习GB-T 397-2022 商品煤质量 炼焦用煤. 而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 1 范围 本文件规定了炼焦用商品煤产品质量等级和技术要求、试验方法、检验规则、标识、运输及贮存。 本文件适用于生产、加工、储运、销售、使用…

MySQL索引事务

一 、 索引 索引是一种特殊的文件&#xff0c;包含着对数据表里所有记录的引用指针。 可以对表中的一列或多列创建索引&#xff0c; 并指定索引的类型&#xff0c;各类索引有各自的数据结构实现。 索引保存的数据结构主要为B树&#xff0c;及hash的方式。 1. 作用 数据库中…

网络安全攻防对抗之隐藏通信隧道技术整理

完成内网信息收集工作后&#xff0c;渗透测试人员需要判断流量是否出得去、进得来。隐藏通信隧道技术常用于在访问受限的网络环境中追踪数据流向和在非受信任的网络中实现安全的数据传输。 一、隐藏通信隧道基础知识 &#xff08;一&#xff09;隐藏通信隧道概述 一般的网络通…

【C++】String类基本接口介绍(多看英文文档)

string目录 目录 如果你很赶时间&#xff0c;那么就直接看我本标题下的内容即可&#xff01;&#xff01; 一、STL简介 1.1什么是STL 1.2STL版本 1.3STL六大组件 1.4STL重要性 1.5如何学习STL 二、什么是string&#xff1f;&#xff1f;&#xff08;本质上是一个类&…

Spring Boot虚拟线程的性能还不如Webflux?

早上看到一篇关于Spring Boot虚拟线程和Webflux性能对比的文章&#xff0c;觉得还不错。内容较长&#xff0c;抓重点给大家介绍一下这篇文章的核心内容&#xff0c;方便大家快速阅读。 测试场景 作者采用了一个尽可能贴近现实操作的场景&#xff1a; 从授权头信息中提取JWT验证…

排序算法:非比较排序(计数排序)

朋友们、伙计们&#xff0c;我们又见面了&#xff0c;本期来给大家解读一下有关排序算法的相关知识点&#xff0c;如果看完之后对你有一定的启发&#xff0c;那么请留下你的三连&#xff0c;祝大家心想事成&#xff01; C 语 言 专 栏&#xff1a;C语言&#xff1a;从入门到精通…

三子棋小游戏(简单详细)

设计总体思路 实现游戏可以一直玩&#xff0c;先打印棋盘&#xff0c;玩家和电脑下棋&#xff0c;最后分出胜负。 如果编写较大的程序&#xff0c;我们可以分不同模块 例如这个三子棋&#xff0c;我们可以创建三个文件 分别为&#xff1a; game.h 函数的声明game.c 函数…

Linux 快捷键

1、快捷键小操作 1.1、ctrl c 强制停止 Linux某些程序的运行&#xff0c;如果想要强制停止它&#xff0c;可以使用快捷键ctrl c 命令输入错误&#xff0c;也可以通过快捷键ctrl c&#xff0c;退出当前输入&#xff0c;重新输入 1.2、ctrl d 退出或登出 可以通过快捷键&…

python的requests响应请求,结果乱码,即使设置了response.encoding也没有用的解决方法

一、问题 如图&#xff1a; 一般出现乱码&#xff0c;我们会有三种解决方式&#xff0c;如下但是图中解决了发现还是不行, response.encodingresponse.apparent_encoding通过看网页源码对response.encodingutf8指定编码格式或者直接通过response.content.decode()来获得源码 出…