Android性能优化—内存优化
一、App内存组成以及管理
Android 给每个 App 分配一个 VM ,让App运行在 dalvik 上,这样即使 App 崩溃也不会影响到系统。系统给 VM 分配了一定的内存大小, App 可以申请使用的内存大小不能超过此硬性逻辑限制,就算物理内存富余,如果应用超出 VM 最大内存,就会出现内存溢出 crash,即OOM。
由程序控制操作的内存空间在 heap 上,分 java heapsize 和 native heapsize 。
1)Java申请的内存在 vm heap 上,所以如果 java 申请的内存大小超过 VM 的逻辑内存限制,就会出现内存溢出的异常。
2)native层内存申请不受其限制, native 层受 native process 对内存大小的限制。
二、Android设备对App的内存限制
如何查看APP的内存大小限制?
1. 主要查看系统配置文件 build.prop ,我们可以通过 adb shell 在 命令行窗口查看
adb shell cat /system/build.prop

2. 通过代码获取
ActivityManager activityManager =
(ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE)
activityManager.getMemoryClass();//以m为单位
3.修改内存限制
修改 \frameworks\base\core\jni\AndroidRuntime.cpp
int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote)
{/** The default starting and maximum size of the heap. Larger* values should be specified in a product property override.*/parseRuntimeOption("dalvik.vm.heapstartsize", heapstartsizeOptsBuf,"-Xms", "4m");parseRuntimeOption("dalvik.vm.heapsize", heapsizeOptsBuf, "-Xmx","16m");//修改这里
}
修改 platform/dalvik/+/eclair-release/vm/Init.c
gDvm.heapSizeStart = 2 * 1024 * 1024; // Spec says 16MB; too big for us.
gDvm.heapSizeMax = 16 * 1024 * 1024; // Spec says 75% physical mem
三、Android内存分配与回收机制
1、内存分配
Android的Heap空间是一个 Generational Heap Memory 的模型,最近分配的对象会存放在 Young
Generation 区域,当一个对象在这个区域停留的时间达到一定程度,它会被移动到 Old Generation ,最后累积一定时间再移动到 Permanent Generation 区域。
1)Young Generation
由一个Eden区和两个Survivor区组成,程序中生成的大部分新的对象都在Eden区中,当Eden区满时,还存活的对象将被复制到其中一个Survivor区,当次Survivor区满时,此区存活的对象又被复制到另一个Survivor区,当这个Survivor区也满时,会将其中存活的对象复制到年老代。
2)Old Generation
一般情况下,年老代中的对象生命周期都比较长。
3)Permanent Generation
用于存放静态的类和方法,持久代对垃圾回收没有显著影响。
总结:内存对象的处理过程如下:
1. 对象创建后在Eden区。
2. 执行GC后,如果对象仍然存活,则复制到S0区。
3. 当S0区满时,该区域存活对象将复制到S1区,然后S0清空,接下来S0和S1角色互换。
4. 当第3步达到一定次数(系统版本不同会有差异)后,存活对象将被复制到Old Generation。
5. 当这个对象在Young Generation区域停留的时间达到一定程度时,它会被移动到Old
Generation,最后累积一定时间再移动到Permanent Generation区域。
2、Generation GC
系统在Young Generation、Old Generation上采用不同的回收机制。每一个Generation的内存区域都有固定的大小。随着新的对象陆续被分配到此区域,当对象总的大小临近这一级别内存区域的阈值时,会触发GC操作,以便腾出空间来存放其他新的对象。
1)执行GC占用的时间与Generation和Generation中的对象数量有关:
1. Young Generation < Old Generation < Permanent Generation
2. Gener中的对象数量与执行时间成正比。
2)Young Generation GC
由于其对象存活时间短,因此基于Copying算法(扫描出存活的对象,并复制到一块新的完全未使用的控件中)来回收。新生代采用空闲指针的方式来控制GC触发,指针保持最后一个分配的对象在YoungGeneration区间的位置,当有新的对象要分配内存时,用于检查空间是否足够,不够就触发GC。
3)Old Generation GC
由于其对象存活时间较长,比较稳定,因此采用Mark(标记)算法(扫描出存活的对象,然后再回收未被标记的对象,回收后对空出的空间要么合并,要么标记出来便于下次分配,以减少内存碎片带来的效率损耗)来回收。
3、Java内存分配模型

4、可达性分析与GCRoots
通过一系列称为“GC Roots”的对象作为起始点,从这些节点向下搜索,搜索所有的引用链,当一个对象到GC Roots没有任何引用链(即GC Roots到对象不可达)时,则证明此对象是不可用的。

GC管理的主要区域是Java堆,一般情况下只针对堆进行垃圾回收。方法区、栈和本地方法区不被GC所管理,因而选择这些区域内的对象作为GC roots,被GC roots引用的对象不被GC回收。
5、GC 回收算法
1)标记清除算法:
1 位置不连续,产生碎片;
2 效率略低;
3 扫描两遍。
2)复制算法:
1 实现简单、运行高效;
2 没有内存碎片;
3 利用率只有一半 。
3)标记整理算法:
1 没有内存碎片;
2 效率偏低;
3 扫描两遍、指针需要调整 。
四、内存三大问题
1、内存抖动
通过Memory Profiler检测,内存波动图形呈锯齿张,频繁GC容易导致页面卡顿。常出现于:循环里创建对象,自定义onDraw创建对象等。
2、内存泄漏
在当前应用周期内不再使用的对象被GC Roots引用,导致不能回收,使实际可使用内存变小;Android内存泄漏分析工具:Memory Profiler和LeakCanary(查看文章,LeakCanary内存泄漏检测框架分析:http://t.csdn.cn/SEpWC);
Android内存泄漏常见场景以及解决方案:
1. 资源性对象未关闭
对于资源性对象不再使用时,应该立即调用它的close()函数,将其关闭,然后再置为null。例如文件、数据库和Bitmap等资源未关闭会造成内存泄漏,此时我们应该在Activity销毁时及时关闭。
2. 注册对象未注销
例如BraodcastReceiver、EventBus未注销造成的内存泄漏,我们应该在Activity销毁时及时注销。
3 .类的静态变量持有大数据对象
尽量避免使用静态变量存储数据,特别是大数据对象,建议使用数据库存储。
4 .单例造成的内存泄漏
优先使用Application的Context,如需使用Activity的Context,可以在传入Context时使用弱引用进行封装,然后在使用到的地方从弱引用中获取Context,如果获取不到,则直接return即可。
5. 非静态内部类的静态实例
该实例的生命周期和应用一样长,这就导致该静态实例一直持有该Activity的引用,Activity的内存资源不能正常回收。此时,我们可以将该内部类设为静态内部类或将该内部类抽取出来封装成一个单例,如果需要使用Context,尽量使用Application Context,如果需要使用Activity Context,就记得用完后置空让GC可以回收,否则还是会内存泄漏。
6. Handler临时性内存泄漏
Message发出之后存储在MessageQueue中,在Message中存在一个target,它是Handler的一个引用,Message在Queue中存在的时间过长,就会导致Handler无法被回收。如果Handler是非静态的,则会导致Activity或者Service不会被回收。并且消息队列是在一个Looper线程中不断地轮询处理消息,当这个Activity退出时,消息队列中还有未处理的消息或者正在处理的消息,并且消息队列中的Message持有Handler实例的引用,Handler又持有Activity的引用,所以导致该Activity的内存资源无法及时回收,引发内存泄漏。解决方案如下所示:
1)使用一个静态Handler内部类,然后对Handler持有的对象(一般是Activity)使用弱引用,这样在回收时,也可以回收Handler持有的对象。
2)在Activity的Destroy或者Stop时,应该移除消息队列中的消息,避免Looper线程的消息队列中有待处理的消息需要处理。需要注意的是,AsyncTask内部也是Handler机制,同样存在内存泄漏风险,但其一般是临时性的。对于类似AsyncTask或是线程造成的内存泄漏,我们也可以将AsyncTask和Runnable类独立出来或者使用静态内部类。
7、容器中的对象没清理造成的内存泄漏
在退出程序之前,将集合里的东西clear,然后置为null,再退出程序。
8、WebView
WebView都存在内存泄漏的问题,在应用中只要使用一次WebView,内存就不会被释放掉。我们可以为WebView开启一个独立的进程,使用AIDL与应用的主进程进行通信,WebView所在的进程可以根据业务的需要选择合适的时机进行销毁,达到正常释放内存的目的。
9、使用ListView时造成的内存泄漏
在构造Adapter时,使用缓存的convertView。
3、内存溢出
即OOM,OOM时会导致程序异常。Android设备出厂以后,java虚拟机对单个应用的最大内存分配就确定下来了,超出这个值就会OOM。
1)OOM原因分类

2)OOM代码分析
Android 虚拟机最终抛出OutOfMemoryError的地方:/art/runtime/thread.cc
void Thread::ThrowOutOfMemoryError(const char* msg) {LOG(WARNING) << StringPrintf("Throwing OutOfMemoryError \"%s\"%s",msg, (tls32_.throwing_OutOfMemoryError ? " (recursive case)" : ""));if (!tls32_.throwing_OutOfMemoryError) {tls32_.throwing_OutOfMemoryError = true;ThrowNewException("Ljava/lang/OutOfMemoryError;", msg);tls32_.throwing_OutOfMemoryError = false;} else {Dump(LOG_STREAM(WARNING)); // The pre-allocated OOME has no stack, sohelp out and log one.SetException(Runtime::Current()->GetPreAllocatedOutOfMemoryError());}
}
堆内存分配失败:/art/runtime/gc/heap.cc
void Heap::ThrowOutOfMemoryError(Thread* self, size_t byte_count,
AllocatorType allocator_type) {// If we're in a stack overflow, do not create a new exception. It wouldrequire running the// constructor, which will of course still be in a stack overflow.if (self->IsHandlingStackOverflow()) {self->SetException(Runtime::Current()->GetPreAllocatedOutOfMemoryErrorWhenHandlingStackOverflow());return;}std::ostringstream oss;size_t total_bytes_free = GetFreeMemory();//为对象分配内存时达到进程的内存上限oss << "Failed to allocate a " << byte_count << " byte allocation with "<< total_bytes_free<< " free bytes and " << PrettySize(GetFreeMemoryUntilOOME()) << "until OOM,"<< " target footprint " <<target_footprint_.load(std::memory_order_relaxed)<< ", growth limit "<< growth_limit_;//没有足够大小的连续地址空间// There is no fragmentation info to log for large-object space.if (allocator_type != kAllocatorTypeLOS) {CHECK(space != nullptr) << "allocator_type:" << allocator_type<< " byte_count:" << byte_count<< " total_bytes_free:" << total_bytes_free;space->LogFragmentationAllocFailure(oss, byte_count);}
}
创建线程失败:/art/runtime/thread.cc
void Thread::CreateNativeThread(JNIEnv* env, jobject java_peer, size_t
stack_size, bool is_daemon) {CHECK(java_peer != nullptr);Thread* self = static_cast<JNIEnvExt*>(env)->GetSelf();// TODO: remove from thread group?env->SetLongField(java_peer,WellKnownClasses::java_lang_Thread_nativePeer, 0);{std::string msg(child_jni_env_ext.get() == nullptr ?StringPrintf("Could not allocate JNI Env: %s", error_msg.c_str()) :StringPrintf("pthread_create (%s stack) failed: %s",PrettySize(stack_size).c_str(),strerror(pthread_create_result)));ScopedObjectAccess soa(env);soa.Self()->ThrowOutOfMemoryError(msg.c_str());}
}
OOM问题比较常出现在:Bitmap没有进行任何压缩处理,直接加载原图到APP上面,而导致OOM;长时间的内存泄漏也是导致OOM问题的原因之一。
相关文章:
Android性能优化—内存优化
一、App内存组成以及管理 Android 给每个 App 分配一个 VM ,让App运行在 dalvik 上,这样即使 App 崩溃也不会影响到系统。系统给 VM 分配了一定的内存大小, App 可以申请使用的内存大小不能超过此硬性逻辑限制,就算物理内存富余&…...
Python自动计算Excel数据指定范围内的区间最大值
本文介绍基于Python语言,基于Excel表格文件内某一列的数据,计算这一列数据在每一个指定数量的行的范围内(例如每一个4行的范围内)的区间最大值的方法。 已知我们现有一个.csv格式的Excel表格文件,其中有一列数据&#…...
FTP文件传输协议
FTP文件传输协议 介绍 将某台计算机中的文件通过网络传送到可能相距很远的另一台计算机中,是一项基本的网络应用,即文件传送文件传输协议(File Transfer Protocol)是因特网上使用得最广泛的文件传输协议 FTP提供交互式访问,允许客户指明文件…...
运维高级--tomcat和jpress
1. 简述静态网页和动态网页的区别。 静态网页:事先创建好的网页,通常通过HTML、CSS和JavaScript等静态文件组成,不需要和服务器进行交互,加载速度快 动态网页:根据用户需求动态生成网页,动态网页通常使用…...
【LeetCode】141. 环形链表 进阶题142. 环形链表 II
141. 环形链表 这道题还是用经典的快慢指针法来做。每次让快的指针走两步,慢的走一步。如果有环,则绝对会在环内的某一节点相遇。思想跟物理知识有点关系,如果有环,则在相对运动过程中,可以相当于慢指针静止࿰…...
MySQL索引1——基本概念与索引结构(B树、R树、Hash等)
目录 索引(INDEX)基本概念 索引结构分类 BTree树索引结构 Hash索引结构 Full-Text索引 R-Tree索引 索引(INDEX)基本概念 什么是索引 索引是帮助MySQL高效获取数据的有序数据结构 为数据库表中的某些列创建索引,就是对数据库表中某些列的值通过不同的数据结…...
TikTok数据分析 | 用好超店有数,生意增长快人一步
TikTok在东南亚崛起之快令人叹服。 在东南亚第一大经济体印度尼西亚,超过200万小商家入驻了TikTok的电商平台; TikTok Shop 以6.9亿美元的收入市场份额超越Lazada成为越南第二大电商平台; 2023年泰国TikTok Shop的销售额一路猛涨ÿ…...
从零开始学Docker(三):DockerFile镜像定制
宿主机环境:RockyLinux 9 前言,定制docker镜像的方式有两种: 手动修改容器内容,然后docker commit提交容器为新的镜像通过在dockerfile中定义一系列的命令和参数构成的脚本,然后这些命令应用于基础镜像,依…...
【Linux】 UDP网络套接字编程
🍎作者:阿润菜菜 📖专栏:Linux系统网络编程 文章目录 一、网络通信的本质(port标识的进程间通信)二、传输层协议UDP/TCP认识传输层协议UDP/TCP网络字节序问题(规定大端) 三、socket编…...
《golang设计模式》第一部分·创建型模式-05-工厂方法模式(Factory Method)
文章目录 1 概述2.1 角色2.2 类图 2 代码示例2. 1 设计2.2 代码2.3 类图 3. 简单工厂3.1 角色3.2 类图3.3 代码示例3.3.1 设计3.3.2 代码3.3.3 类图 1 概述 工厂方法类定义产品对象创建接口,但由子类实现具体产品对象的创建。 2.1 角色 Product(抽象产…...
Kubernetes 概述
1、K8S 是什么? K8S 的全称为 Kubernetes (K12345678S) 作用 用于自动部署、扩展和管理“容器化(containerized)应用程序”的开源系统。 可以理解成 K8S 是负责自动化运维管理多个容器化程序(比如 Docker)的集群&#…...
Electron + Vue3 + Vite + TS 构建桌面应用
之前是使用React、Electron、TS和webpack来构建桌面应用的。虽然功能齐全,但是打包等等开发的体验不太理想,总感觉太慢了。作为一个开发者,我们总是希望,执行构建命令后,可以快速打包或者启动本地应用,且通过更少的配置,来完成开发体验。 现在的vite已经得到广泛的应用…...
springboot访问请求404的原因
是记录,可能出现错误 可能出现的原因 1.你请求的URL路径不对,比如说你请求的路径是/usr/list,GET方法,但是你UserController上面的RequestMapping是这个样子:RequestMapping(“user”),有可能哈 2.前端的请求时GET方法,后端对应的处理函数的方…...
网络安全零基础该如何自学?
一、为什么选择网络安全? 这几年随着我国《国家网络空间安全战略》《网络安全法》《网络安全等级保护2.0》等一系列政策/法规/标准的持续落地,网络安全行业地位、薪资随之水涨船高。 未来3-5年,是安全行业的黄金发展期,提前踏入…...
Git(丢失stash数据恢复)
在这里总结一下昨天遇到的问题,我本想将本地代码push到远端仓库,依次运行了以下命令 git init //初始化 git add . //将本地代码添加到暂存区 git commit -m 注释 //将暂存区内容添加到本地仓库中。 结果这时发生了代码冲突,我的代码全没了&a…...
Maven依赖管理
依赖特性: 1、依赖配置 2、依赖传递 3、可选依赖 4、排除依赖 5、依赖范围...
【电网技术复现】考虑实时市场联动的电力零售商鲁棒定价策略(Matlab代码实现)
💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...
R语言中数据重塑(长宽表的转化)
学习笔记,仅供学习使用。 目录 1-什么是整洁的数据? 2-宽表变成长表 示例1: 示例2: 示例3: 3-长表变宽表 示例1: 示例2: 1-什么是整洁的数据? 按照Hadley的表述…...
C# Blazor 学习笔记(10):依赖注入
文章目录 前言Blazor 依赖注入依赖注入用于解决什么问题?依赖注入的生命周期。测试代码 总结 前言 Blazor 具有前后端不分离模式,但是如何直接调用需要一定的设置 Blazor 依赖注入 依赖注入在spring里面很常见,毕竟.NET 是个巨型融合怪。只…...
接口请求(get、post、head等)详解
一.接口请求的六种常见方式: 1、Get 向特定资源发出请求(请求指定页面信息,并返回实体主体) 2、Post 向指定资源提交数据进行处理请求(提交表单、上传文件),又可能导致新的资源的建…...
网络编程(Modbus进阶)
思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…...
测试微信模版消息推送
进入“开发接口管理”--“公众平台测试账号”,无需申请公众账号、可在测试账号中体验并测试微信公众平台所有高级接口。 获取access_token: 自定义模版消息: 关注测试号:扫二维码关注测试号。 发送模版消息: import requests da…...
在软件开发中正确使用MySQL日期时间类型的深度解析
在日常软件开发场景中,时间信息的存储是底层且核心的需求。从金融交易的精确记账时间、用户操作的行为日志,到供应链系统的物流节点时间戳,时间数据的准确性直接决定业务逻辑的可靠性。MySQL作为主流关系型数据库,其日期时间类型的…...
【Python】 -- 趣味代码 - 小恐龙游戏
文章目录 文章目录 00 小恐龙游戏程序设计框架代码结构和功能游戏流程总结01 小恐龙游戏程序设计02 百度网盘地址00 小恐龙游戏程序设计框架 这段代码是一个基于 Pygame 的简易跑酷游戏的完整实现,玩家控制一个角色(龙)躲避障碍物(仙人掌和乌鸦)。以下是代码的详细介绍:…...
树莓派超全系列教程文档--(61)树莓派摄像头高级使用方法
树莓派摄像头高级使用方法 配置通过调谐文件来调整相机行为 使用多个摄像头安装 libcam 和 rpicam-apps依赖关系开发包 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 配置 大多数用例自动工作,无需更改相机配置。但是,一…...
镜像里切换为普通用户
如果你登录远程虚拟机默认就是 root 用户,但你不希望用 root 权限运行 ns-3(这是对的,ns3 工具会拒绝 root),你可以按以下方法创建一个 非 root 用户账号 并切换到它运行 ns-3。 一次性解决方案:创建非 roo…...
如何为服务器生成TLS证书
TLS(Transport Layer Security)证书是确保网络通信安全的重要手段,它通过加密技术保护传输的数据不被窃听和篡改。在服务器上配置TLS证书,可以使用户通过HTTPS协议安全地访问您的网站。本文将详细介绍如何在服务器上生成一个TLS证…...
12.找到字符串中所有字母异位词
🧠 题目解析 题目描述: 给定两个字符串 s 和 p,找出 s 中所有 p 的字母异位词的起始索引。 返回的答案以数组形式表示。 字母异位词定义: 若两个字符串包含的字符种类和出现次数完全相同,顺序无所谓,则互为…...
06 Deep learning神经网络编程基础 激活函数 --吴恩达
深度学习激活函数详解 一、核心作用 引入非线性:使神经网络可学习复杂模式控制输出范围:如Sigmoid将输出限制在(0,1)梯度传递:影响反向传播的稳定性二、常见类型及数学表达 Sigmoid σ ( x ) = 1 1 +...
【C++从零实现Json-Rpc框架】第六弹 —— 服务端模块划分
一、项目背景回顾 前五弹完成了Json-Rpc协议解析、请求处理、客户端调用等基础模块搭建。 本弹重点聚焦于服务端的模块划分与架构设计,提升代码结构的可维护性与扩展性。 二、服务端模块设计目标 高内聚低耦合:各模块职责清晰,便于独立开发…...




