【安卓笔记】OOM与内存优化

0. 环境:

电脑:Windows10

Android Studio: 2024.3.2

编程语言: Java

Gradle version:8.11.1

Compile Sdk Version:35

Java 版本:Java11

1.什么是OOM

OOM即 OutOfMemoryError 内存溢出错误。常见于一些

  • 资源型对象未关闭
  • 注册对象未注销(反注册)
  • 类的静态变量持有大量数据对象
  • 单例造成的内存泄漏
  • 非静态内部类的静态实例
  • Handler临时性内存泄漏
  • 容器中的对象没清理造成的内存泄漏
  • WebView
  • 使用ListView时造成的内存泄漏

等导致的。


Android内存泄漏常见场景及解决方案

  • 资源性对象未关闭

        常见场景:例如:Bitmap,使用后就不管了。

        解决方案:对于资源性对象不再使用时,应该立即close(),然后设置为null。例如Bitmap。如果未关闭则容易造成内存泄漏。最好是在activity中的onDestroy()中将Bitmap给close()并设置成null。

  • 注册对象未注销

        常见场景:例如:BroadcastReceiver、EventBus未注销造成的内存泄漏。

        解决方案:应该在activity的onDestroy()中及时注销/反注册

  • 类的静态变量持有大数据对象

        常见场景:静态变量存储数据

        解决方案:尽量避免使用静态变量存储数据。如果是大数据对象,建议使用数据库存储。

        关于数据库,可以查看我的这篇文章:【安卓笔记】Room数据库的基本使用-CSDN博客

  • 单例造成的内存泄漏

        常见场景:使用了Activity的Context,但是被外部持有,导致无法回收。

        解决方案:优先使用Application的Context,如需使用Activity的Context,可以在传入Context时使用弱引用进行封装,然后在使用到的地方从弱引用中获取Context。如果获取不到,则return即可。

  • 非静态内部类的静态实例

        常见场景:实例化了 非静态内部类的静态实例

        解决方案:该实例的生命周期和应用一样长,这就会导致该静态实例一直持有该Activity的引用,activity的内存资源不能正常回收。此时,我们可以将该内部类设置为静态内部类或者将该内部类抽取出来封装成一个单例,如果需要使用Context,尽量使用Application的Context。如果一定要使用Activity的Context,记得用完后要置空,让GC可以回收。

  • Handler临时性内存泄漏

        常见场景:Handler是非静态,并且创建在Activity或者Service中。

        解决方案:Message发出之后存储在MessageQueue中。在Message中存在一个target,它是Handler的一个引用。Message在Queue中存在的时间过长,就会导致Handler无法被回收。如果Handler是非静态的,则会导致Activity或者Service不会被回收。并且消息队列实在一个Looper线程中不断地轮询处理消息。当这个Acitivity退出时,消息队列中还有未处理的消息或者正在处理的消息,并且消息队列中的Message持有Handler实例的引用,Handler又持有Activity的引用,所以导致该Activity的内存资源无法及时回收,引发内存泄漏。

  • 容器中的对象没清理造成的内存泄漏

        解决方案:在退出程序之前,将集合.clear(),然后设置为null,再退出程序。

  • WebView

        常见场景:WebView大多都存在内存泄漏的问题。

        解决方案:在应用中只要使用一次WebView,内存就不会被释放掉。我们可以为WebView开启一个单独的进程,使用AIDL与应用的主进程进行通信。WebView所在的进程可以根据业务的需要选择合适的时机进行销毁,达到正常释放内存的目的。

  • 使用ListView时造成的内存泄漏

        解决方案:在构造Adapter时,使用缓存的convertView


OOM比较难找到问题的根源,就是因为大部分OOM的问题,都是内存先被吃完了,最后由正常代码引发OOM,所以,日志中基本上只能看到最后的触发导致。这往往不是根本原因。

OOM的原因分类:

1. Java堆内存溢出(内存不够)

2. 无足够连续内存空间(内存够,但大部分都是碎片化)

3. FD数量超出限制(文件句柄(FD: 文件句柄)泄漏)

4. 线程数量超出限制(线程数量泄漏)

5. 虚拟内存不足

2. 一些散装知识

Java的对象生命周期

Java的对象生命周期(Java Object Life Cycle)(有大概了解就行了)

Created 创建

in use 应用

Invisible 不可见

Unreachable 不可达

Collected 收集

Finalized 终结

Deallocated 对象空间重新分配  


Java的四种引用

强引用:=

        强引用:被GC扫描到了,也不会回收。容易造成OOM

软引用:SoftReference

        软引用:内存不足时,会回收

弱引用:WeakRefoerence

        弱引用:GC扫描到了,会被回收掉

虚引用:PhantomReference 

        虚引用:相当于没有被引用

3. Android内存分析命令

  • dumpsys meminfo
  • procrank
  • cat/proc/meminfo
  • free
  • showmap
  • vmstat
  • top -n 1

3.0 一些条件

1. 手机需要root(Android模拟器也行)

2. 需要使用adb

3. 进入 adb shell,再操作以下命令

3.1 dumpsys meminfo

功能:大概判断哪个页面有内存泄漏

内存指标概念

item全称含义等价
USSUnique Set Size物理内存进程独占的内存
PSSProportional Set Size物理内存PSS = USS + 按比例包含共享库
RSSResident Set Size物理内存RSS = USS + 包含共享库
VSSVirtual Set Size虚拟内存VSS = RSS + 未分配实际物理内存

总结:VSS >= RSS >= PSS >= USS,但 /dev/kgsl-3d0部分必须考虑VSS

一般只看PSS。

这边说一下如何判断activity中的view是否有内存泄漏:

例如从Aactivity进入Bactivity,再返回再进入,多次之后。

使用dumpsys meminfo命令,在命令提示符窗口最下方找到Objects组,查看ViewRootImpl数据,如果该数据在每次activity进入时都会增加。则代表该activity持有的view内存泄漏了

3.2 procrank

功能:获取所有进程的内存使用情况,顺序以PSS的大小从大到小排列。

procrank比dumpsys meminfo,更详细输出 VSS/RSS/PSS/USS内存指标。

例如:最后一行输出以下6个指标:

totalfreebufferscachedshmemslab
2857032K998088K78060K78060K312K92392K

3.3 cat /proc/meminfo

功能:查看更加详细的内存信息

3.4 free

功能:查看可用内存。单位KB。

该命令比较简单、轻量,专注于查看剩余内存的情况。数据来源于3.3 cat /proc/meminfo

3.5 showmap

 功能:用于查看虚拟地址区域的内存情况

用法:

showmap -a [pid]

  • start addr和end addr: 分别代表进程空间的起止虚拟地址
  • virtual size/RSS/PSS:具体看3.1中的介绍
  • shared clean:代表多个进程的虚拟地址可指向这块物理空间
  • shared:共享数据
  • private:该进程私有数据
  • clean:干净数据,该内存数据与disk数据一致。当内存紧张时,可直接释放内存,不需要回写到disk
  • dirty:脏数据,需要回写到disk,才能被释放。 

3.6 vmstat

功能:不仅可以查看内存情况,还可以查看进程运行队列、系统切换、CPU时间占比等。(可以周期性动态输出)

用法:

vmstat [ -n iterations ] [ -d delay ] [ -r header_repeat ]

解释:

-n iterations:数据循环输出的次数

-d delay:两次数据间的延迟时长(单位:s)

-r header_repeat:循环次数

3.7 top -n 1 

op命令是Linux下常用的性能分析工具,可以实时显示系统中各个进程的资源占用情况。

功能:显示当前系统正在执行的进程的相关信息:进程ID、内存占用率、CPU占用率等

用法:

top [参数]

参数:

-b 批处理

-c 显示完整的治命令

-l 忽略失效过程

-s 保密模式

-S 累积模式

-i<时间> 设置时间间隔

-u<用户名> 指定用户名

-p<进程号> 指定进程

-n<次数> 循环显示的次数 

3.8 总结

1. dumpsys meminfo 适用场景:查看进程的oom adj、dalvik/native等区域内存使用情况、某个进程/apk的内存情况

2. procrank 适用场景:查看进程的VSS/RSS/PSS/USS

3. cat /proc/meminfo 适用场景:查看系统的详尽内存信息,包含内核情况

4. free 适用场景:只查看系统的可用内存

5. showmap 适用场景:查看进程的虚拟地址空间的内存分配情况

6. vmstat 适用场景:周期性打印进程运行队列、系统切换、CPU时间占比等情况

4. 常见分析工具

1. Memory Analyzer Tools

        开发过程中,本地分析

2. Memory Profiler(本文不介绍)

        Android Studio 自带的分析工具

3. LeakCanary(本文不介绍)

        线上版本集成该工具分析

5. MAT(Memory Analyzer Tools)工具的使用

工具下载地址:Downloads | The Eclipse Foundation

5.1 .hprof文件分析

        5.1.1 储存.hprof文件到手机内

public static void createDumpFile(Context context) {// 目录路径String LOG_PATH = "/dump.gc/";// 文件名称SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd_HH.mm.ssss");String createTime = sdf.format(new Date(System.currentTimeMillis()));// 手机环境String externalStorageState = Environment.getExternalStorageState();// 如果有SD卡if (Environment.MEDIA_MOUNTED.equals(externalStorageState)) {// 文件路径File file = new File(Environment.getExternalStorageDirectory().getPath() + LOG_PATH);if (!file.exists()) {// 没有该路径就创建file.mkdirs();}String hprofPath = file.getAbsolutePath();if (!hprofPath.endsWith("/")) {hprofPath += "/";}// 文件名称hprofPath += createTime + ".hprof";try {// dump储存.hprof文件android.os.Debug.dumpHprofData(hprofPath);} catch (IOException e) {throw new RuntimeException(e);}}
}

在执行完需要查看是否OOM的代码后,再调用该函数,就可以在 手机路径下看到.hprof文件:

sdcard/dump.gc/yyyy-MM-dd_HH.mm.ssss.hprof

        5.1.2  MAT工具打开文件

此时无法用MAT工具直接打开文件,需要转换。转换的命令如下:

hprof-conv dump1.hprof converted-dump2.hprof

解释

dump1.hprof:转换前的文件

dump2.hprof:转换后的文件

如果上面命令无效,请使用以下命令:

hprof-conv.exe dump1.hprof dump2.hprof

解释:

dump1.hprof:转换前的文件

dump2.hprof:转换后的文件

(提一嘴,该命令的环境路径在 android/sdk/platform-tools 下) 

转换后就可以打开了。 

        5.1.3 查看.hprof文件

其中 Problem Suspect 为工具猜想泄漏可能。不重要

首先点击柱状图,按对象查看,出现下面的列表:(柱状图右侧的按钮,自行测试一下就好了。例如:按线程查看)

然后第二步选择按package

 选择完这个后,界面就会变成如下:

这样,就可以比较直观、比较友好的查看包名下的对象了。

        5.1.4 具体对象查看 

我们在该页面下,右键鼠标,选择List objects后,右侧有两个选项,如下图:

with outgoing references

查看该对象持有谁

with incoming references 

查看谁持有该对象 

 5.2 MAT中浅堆和深堆

Shallow Heap:浅堆

对象本身占用的内存

Retained Heap:深堆

统计结果:本身占用内存 + 引用的对象占用内存

例如下图:

假设ABCDEFG各占用10个内存大小。但是A持有BC,B持有DE,C持有FG。所以A的浅堆为自身:10,深堆为70(A自身+BDE引用 +CFG引用) 

在工具中,就是这一部分数据:

 5.2.1 找到深堆异常

通过对深堆的查看,就可以知道 哪个对象占用内存过大。然后通过incoming和outging的引用关系查看,就可以查询上下关系,找到内存泄漏的点。例如某个对象被多处引用,被持久化导致无法释放。

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

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

相关文章

持续集成CI与自动化测试

Python接口自动化测试零基础入门到精通&#xff08;2025最新版&#xff09;

Spring 策略模式实现

Spring 策略模式实现&#xff1a;工厂方法与自动注入详解 1. 背景介绍 在复杂的业务系统中,我们常常需要根据不同的场景选择不同的处理策略。本文将详细介绍在 Spring 框架中实现策略模式的两种主要方法。 2. 方案一: 手动注册工厂模式 2.1 定义工厂类 Component public class …

机器学习——线性回归(LinearRegression)

Python 线性回归详解&#xff1a;从原理到实战线性回归&#xff08;Linear Regression&#xff09;是机器学习中最基础也是最重要的算法之一&#xff0c;广泛应用于预测分析领域&#xff0c;例如房价预测、销售额预测等。本文将带你从理论出发&#xff0c;用 Python 手把手实现…

H.264视频的RTP有效载荷格式(翻译自:RFC6184 第5节 RTP有效载荷格式)

RTP协议格式 RFC地址&#xff1a;https://datatracker.ietf.org/doc/html/rfc6184 RTP报头的格式在RFC3550中指定 0 1 2 30 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1------------------------…

秒级构建消息驱动架构:描述事件流程,生成 Spring Cloud Stream+RabbitMQ 代码

在消息驱动架构开发中&#xff0c;Spring Cloud Stream 与 RabbitMQ 的整合往往需要手动配置绑定器、定义消息通道、编写消费逻辑&#xff0c;流程繁琐且易出错。而飞算JavaAI 作为高效的 IDE 插件&#xff0c;能让开发者通过自然语言描述事件流程&#xff0c;自动生成可运行的…

从零搭建3D激光slam框架-基于mid360雷达节点实现

目录 MID360雷达介绍 雷达SDK编译与测试 雷达驱动的修改、编译与测试 去ros的编译方式 livox_ros_driver2的代码框架介绍 livox_ros_driver2编译 雷达IP配置文件介绍 常见问题介绍 优化改进 MID360雷达介绍 1 硬件介绍&#xff1a; livox-mid360是大疆的一款非重复扫描…

【Spring】日志级别的分类和使用

文章目录介绍日志级别的分类日志级别的顺序日志级别的使用介绍 日志级别代表着日志信息对应问题的严重性&#xff0c;为了更快的筛选符合目标的日志信息 试想一下这样的场景&#xff0c;假设你是一家 2 万人公司的老板&#xff0c;如果每个员工的日常工作和琐碎的信息都要反馈…

【C++】第十九节—一文万字详解 | AVL树实现

好久不见&#xff0c;我是云边有个稻草人&#xff0c;偶尔中二博主与你分享C领域专业知识^(*&#xffe3;(oo)&#xffe3;)^ 《C》—本篇文章所属专栏—持续更新中—欢迎订阅~喔 目录 一、AVL的概念 二、AVL树的实现 2.1 AVL树的结构 2.2 AVL树的插入 【AVL树插入⼀个值…

【Delphi】快速理解泛型(Generics)

Delphi的泛型&#xff08;generics&#xff09;是一项强大的特性&#xff0c;它使得代码更加灵活、类型安全&#xff0c;并且可以实现各种通用的数据结构和算法。下面我将为你详细介绍Delphi中的泛型&#xff0c;包括基本概念、语法、常用实例&#xff0c;以及使用建议。Delphi…

Java Stream流的使用

获取Stream流 单列集合直接使用stream()方法 List<String> list Arrays.asList("a", "b", "c"); Stream<String> stream list.stream(); // 获取顺序流数组使用静态方法Arrays.stream() String[] array {"a", "b&…

前端实现添加水印,两种方式

一、自定义指令的方式/*需求&#xff1a;给整个页面添加背景水印。思路&#xff1a;1、使用 canvas 特性生成 base64 格式的图片文件&#xff0c;设置其字体大小&#xff0c;颜色等。2、将其设置为背景图片&#xff0c;从而实现页面或组件水印效果使用&#xff1a;设置水印文案…

使用LangChain构建法庭预定智能体:结合vLLM部署的Qwen3-32B模型

文章目录 技术架构概述 核心实现步骤 1. 配置vLLM与Qwen3-32B模型 2. 定义工具(Tools) 3. 构建Agent系统 4. 运行与交互 关键技术亮点 1. 工具调用自动化 2. Hermes解析器优势 3. 对话记忆管理 实际运行效果 性能优化建议 扩展应用场景 总结 在人工智能应用开发中,如何让大语…

vscode开发微信小程序

下载插件 插件下载位置 1.微信小程序开发工具 2.vscode weapp api 3.vscode wxml 4.vscode-wechat 创建项目 终端运行命令 cd 到要创建项目的目录执行命令&#xff1a;vue create -p dcloudio/uni-preset-vue test test就是项目名称 选择默认模板&#xff0c;回车 出现下图这…

板凳-------Mysql cookbook学习 (十二--------3_3)

https://cloud.tencent.com/developer/article/1454690 侯哥的Python分享 # 创建节点 class Node(object):def __init__(self,item):self.element itemself.next None# 创建单链表类 class SingleLinkList(object):def __init__(self):self.header Noneself.length 0# 1、判…

Flutter开发实战之CI/CD与发布流程

第12章:CI/CD与发布流程 在前面的章节中,我们学习了Flutter应用开发的各个方面,从基础UI构建到复杂的状态管理,从网络请求到本地存储。现在,我们将探讨一个同样重要但常被忽视的话题:如何将我们精心开发的应用高效、可靠地发布到各大应用商店。 想象一下,你花费了数月…

ElasticSearch 的3种数据迁移方案

在实际工作中&#xff0c;我们经常会遇到需要将自建的 Elasticsearch 迁移上云&#xff0c;或者迁移到其他 ES 集群的情况。这时&#xff0c;选择合适的数据迁移方案就显得尤为重要啦。今天就来给大家介绍三种常用的迁移方案&#xff0c;分别是 COS 快照、logstash 和 elastics…

MySQL 中的“双路排序”与“单路排序”:原理、判别与实战调优

一句话导读 ORDER BY 不能走索引时&#xff0c;MySQL 会在 Server 层做一次 filesort。内部实现分 单路&#xff08;全字段&#xff09; 与 双路&#xff08;rowid&#xff09; 两种&#xff1b;了解它们的触发条件、判别方法与调优思路&#xff0c;是 SQL 性能优化的必修课。一…

OpenLayers 综合案例-信息窗体-弹窗

看过的知识不等于学会。唯有用心总结、系统记录&#xff0c;并通过温故知新反复实践&#xff0c;才能真正掌握一二 作为一名摸爬滚打三年的前端开发&#xff0c;开源社区给了我饭碗&#xff0c;我也将所学的知识体系回馈给大家&#xff0c;助你少走弯路&#xff01; OpenLayers…

GaussDB 开发基本规范

1 集中式1.1数据库价值特性推荐特性分类特性列表说明表类型PARTITION表数据分区存储引擎行存储按行顺序存储表&#xff0c;建议点查&#xff0c;增删改操作较多场景下使用事务事务块显式启动事务单语句事务不显式启动事务&#xff0c;单语句即为事务扩容在线扩容扩节点和数据重…

工作中使用git可能遇到的场景

1.main历史发布版本出问题需要查看&#xff0c;怎么切换历史发布版本&#xff1f;git reset --hard commitid 更新本地库和代码2.A分支的代码已经做过一些功能&#xff0c;想迁移到B分支当前在A分支git checkout B &#xff08;切换到B分支&#xff09;git cherry-pick A的com…