Android性能优化—内存优化

2023-11-12

一、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、内存抖动

每创建一个对象就会分配一个内存,可用内存就少用了一块,当程序占用的内存达到一定的临界值
就会出发GC 内存频繁分配和回收导致内存不稳定瞬间产生大量的对象会严重占用Young Generation的内存区域,当达到阀值,剩余空间不够的时候,也会触发GC。触发GC时会停止用户线程,导致APP卡顿,甚至有可能出现OOM。通过Memory Profiler检测,内存波动图形呈锯齿张。常出现于:循环里创建对象,自定义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, so
		help 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 would
	require 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问题的原因之一。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android性能优化—内存优化 的相关文章

  • 如何为ScrollView放置固定图像背景?

    我应该想要滚动视图滚动 而不是背景中的图像 将图像添加到滚动视图框架之前的视图层次结构的较高位置
  • Android Studio 3.0 Canary 9 - 无法解析包

    我在 Android Studio 3 0 Canary 9 中遇到几个错误 这些错误是 无法解析 android 软件包 下面列出了一些错误 我刚刚安装了 SDK 的所有额外软件包 但仍然收到 gradle 构建错误 Error 82 1
  • android中向sqlite中插入大量数据

    目前 我必须一次向我的 Android 中插入超过 100 亿条数据 然而 内存不足的问题会使程序崩溃 sqlite 插入测试非常简单 只需使用 for 循环生成 sql 插入命令并通过 开始 和 提交 进行包装 private Array
  • Adobe 是否为其 PDF 阅读器提供 Android SDK 或 API? [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 我希望能够在我们的应用程序内的视图中显示本地 PDF 文件 在 Android 4 03 下的平板电脑上运行 目前 我们将 Adob eR
  • 找不到处理意图 com.instagram.share.ADD_TO_STORY 的活动

    在我们的 React Native 应用程序中 我们试图让用户根据视图 组件中的选择直接将特定图像共享到提要或故事 当我们尝试直接使用 com instagram share ADD TO FEED 进行共享时 它以一致的方式完美运行 但是
  • Android 模拟器插件无法初始化后端 EGL 显示

    我在 Cloudbees 上设置了 Jenkins 作业 并且可以在那里成功签出并编译我的 Android 项目 现在我想在 android 模拟器中运行一些 JUnit 测试并添加 Android 模拟器插件 我将 显示模拟器窗口 选项设
  • 是否必须删除 Intent extra?

    这可能是一个愚蠢的问题 但是是否有一条规则规定消费活动必须显式删除 Intent 额外内容 或者只有在回收 Intent 对象时才如此 换句话说 如果我总是通过执行以下操作来链接到下一个活动 Intent i new Intent MyCu
  • 带有 EditText 和 Spinner 的对话框

    我有一个按钮 单击后会弹出一个对话框 我希望对话框有一个EditText and a Spinner对话框内 我不知道如何设置它的视图 我有一个代码AlertDialog它有效 只是EditText and Spinner我需要将其放入其中
  • 如何使用 Cordova 获取当前安装的应用程序的版本?

    我已经找到了应用程序可用性插件 https github com ohh2ahh AppAvailability它主要检查用户是否在其设备上安装了某个应用程序 是否有可能获得应用程序的当前版本 开发者名称 重要 以及所有可能的信息 一般来说
  • 原色(有时)变得透明

    我正在使用最新的 SDK 版本 API 21 和支持库 21 0 2 进行开发 并且在尝试实施新的材料设计指南时遇到了麻烦 材料设计说我需要有我的primary color and my accent color并将它们应用到我的应用程序上
  • 如何发布Android .aar源以使Android Studio自动找到它们?

    我正在将库发布到内部 Sonatype Nexus 存储库 Android Studio 有一个功能 可以自动查找通过 gradle 引用的库的正确源 我将 aar 的源代码作为单独的 jar 发布到 Nexus 但 Android Stu
  • Android Studio 0.4.3 Eclipse项目没有gradle

    在此版本之前 在 Android Studio 中按原样打开 Eclipse 项目似乎很容易 无需任何转换 我更喜欢 Android Studio 环境 但我正在开发一个使用 eclipse 作为主要 IDE 的项目 我不想只为这个项目下载
  • Android访问远程SQL数据库

    我可以直接从 Android 程序访问远程 SQL 数据库 在网络服务器上 吗 即简单地打开包含所有必需参数的连接 然后执行 SQL 查询 这是一个私人程序 不对公众开放 仅在指定的手机上可用 因此我不担心第三方获得数据库访问权限 如果是这
  • Android 中麦克风的后台访问

    是否可以通过 Android 手机上的后台应用程序 服务 持续监控麦克风 我想做的一些想法 不断聆听背景中的声音信号 收到 有趣的 音频信号后 执行一些网络操作 如果前台应用程序需要的话 后台应用程序必须能够智能地放弃对麦克风的访问 除非可
  • Android向menuItem添加子菜单,addSubMenu()在哪里?

    我想根据我的参数以编程方式将 OptionsMenu 内的子菜单添加到 menuItem 中 我检查了android sdk中的 MenuItem 没有addSubMenu 方法 尽管你可以找到 hasSubMenu 和 getSubMen
  • 如何根据 gradle 风格设置变量

    我想传递一个变量test我为每种风格设置了不同的值作为 NDK 的定义 但出于某种原因 他总是忽略了最后味道的价值 这是 build gradle apply plugin com android library def test andr
  • 增加活动的屏幕亮度

    显然 Android 操作系统中至少有三种不同的技术可以改变屏幕亮度 其中两个在纸杯蛋糕之后不再起作用 而第三个被接受的技术显然有一个错误 我想在单视图活动开始时增加屏幕亮度 然后在活动结束时将亮度恢复为用户设置 没有按钮 没有第二个视图或
  • 捕获的图像分辨率太大

    我在做什么 我允许用户捕获图像 将其存储到 SD 卡中并上传到服务器 但捕获图像的分辨率为宽度 4608 像素和高度 2592 像素 现在我想要什么 如何在不影响质量的情况下获得小分辨率图像 例如我可以获取或设置捕获的图像分辨率为原始图像分
  • 如何将 google+ 登录集成到我的 Android 应用程序中?

    大家好 实际上我需要通过我的应用程序从 google 登录人们 现在我阅读了 google 上的文档 其中指出 要允许用户登录 请将 Google Sign In 集成到您的应用中 初始化 GoogleApiClient 对象时 请求 PL
  • Crashlytics 出现 Android Studio 构建错误

    我正在尝试将 CrashLytics 与 Android Studio 和 gradle 一起使用 但出现一个令人困惑的错误 java lang NoSuchMethodError 我的 build gradle 是 buildscript

随机推荐

  • 安装MySQL绿色版本,不用装软件、不用装软件、不用装软件

    安装MySQL绿色版本 不用装软件 不用装软件 不用装软件 MySQL也有绿色版本的 不用安装MySQl软件 下载后解压 简单配置和后 就可以直接使用 1 第一步下载免安装版本 官网链接如下 MySQL Download MySQL Com
  • Centos7升级ssh

    1 备份原有文件 mkdir ssh bak cp etc ssh etc ssh bak 2 安装并启动Telnet yum install telnet telnet server xinetd y systemctl start te
  • Unity 2D射线基本使用和画线

    2D射线可以检测到挂载了Collider2D的对象 包括isTrigger 2D射线常用的是 Physics2D Raycast 函数 它的描述为 向场景中的碰撞体投射射线 射线投射 类似于从空间中的某个点朝特定方向发射一条光束 在该过程中
  • golang---http服务创建,路由注册,请求处理源码

    服务器创建部分 main func main StartHttpServer StartHttpServer func StartHttpServer http HandleFunc go httpHandle http ListenAnd
  • STM32F407控制180度舵机

    其中主要分为两部分 时钟的初始化和主函数的控制部分 时钟的初始化 选用TIM14时钟 F9引脚作为信号控制引脚 void TIM14 PWM Init u32 arr u32 psc GPIO InitTypeDef GPIO InitSt
  • vue树形控件【页面渲染】

    在Vue中渲染树形控件的常见方法是使用递归组件 递归组件基于组件自身调用自身的方式来构建树形结构 以下是一个简单的树形结构的组件示例
  • rand()的最大值

    rand 函数是一个在开发的时候比较常用的函数 但这个函数返回随机数的取值范围并非多大的值都可以 在工作修一个抽奖活动bug的时候曾经遇到这样一种情况 当总权重大于rand的最大值2 16 32767的时候 rand返回的值将不会大于327
  • vue element插件this.$confirm用法(取消也可以发请求)

    场景 弹出框的两个按钮都能分别请求接口 最简单的弹出框就是 确定 取消 一般用户点击确定才会继续接下来的动作 点击取消则不做任何动作 即不会请求接口 如
  • Chromium OS初体验 就是一款Linux

    好奇 弄了一个Chromium OS for VMWare 玩玩 发现Chromium OS并非像我之前想象的一样 并非完全是一个自主研发的独立操作系统 启动 Chromium OS 时 vmware 被设置成图形模式 但一片漆黑什么都看不
  • 【OpenCV】Blob斑点检测学习笔记

    设置 SimpleBlobDetector 参数 params cv2 SimpleBlobDetector Parms 改变阈值 params minThreshold 自定义下阈值 params maxThreshold 自定义上阈值
  • stm32实现Systick的毫秒级延时和微妙级延时

    学习目标 stm32实现Systick的毫秒级延时和微妙级延时 学习内容 1 Systick 工作原理 Systick 系统定时器 是ARM Cortex M3 M4 内核的一个外设 因为所有的CM3 M4内核的单片机都带有这个定时器 这使
  • 若依微服务增强swagger增强集成knife4j

    1 项目pom xml中增加
  • iOS音视频—Shell脚本语言(语法-字符串)

    In every walk with nature one receives far more than he seeks 每一次和自然同行 都会有意外的收货 Shell脚本语言 语法 字符串 1 单引号 name wt echo name
  • 代码随想录算法训练营19期第37天

    738 单调递增的数字 代码随想录 初步思路 贪心 总结 还需要考虑遍历顺序 只有从后向前遍历才能重复利用上次比较的结果先排序 用时 45分钟 968 监控二叉树 代码随想录 初步思路 仅仅贪心好像还是不够 总结 二刷三刷再来 用时 60分
  • 【智领信创】用友 U8 cloud &亚信科技 AntDB联合产品强势来袭,0元购活动惠及陕、鲁

    近日 用友U8 cloud信创云ERP新品体验会在西安 济宁两市成功举办 用友U8 cloud 亚信科技AntDB联合产品精彩亮相 为陕 鲁两省行业客户提供领先信创解决方案的同时 也为两省客户带来极具诚意的优惠方案 U8 cloud Ant
  • 连接阿里云服务器MySql数据库

    首先先说一个坑 也是自己很久没有使用linux原因导致的 自己也是的 最近忙于工作 买了阿里云服务器之后一直都没有去弄了 感觉自己白花钱了 废话不多说了 直接进入正题 第一 肯定要看你的mysql数据库是否启动 才能确定是否能够连接 一下有
  • Outlook 突然打不开

    打开电脑正准备上班然后outlook崩了 报错建议我重装软件 问题是现在用的都是365全家桶 也没办法单独重装一个outlook 盲试了一把repair居然修好了 再后来就经常用到它T T 不是什么好事 首先有几种临时解决方法 如果时间很紧
  • mysql配置超详细教程_MySQL系列(一):超详细、非常适合入门的MySQL安装、环境配置教程...

    MySQL系列教程不定期更新 欢迎关注 一 安装环境 Windows 10 专业版 64位 二 下载MySQL 1 访问MySQL官网 网址为 http www mysql com 2 点击页面上方的 DOWMLOADS 3 选择 MySQ
  • 苹果全新iPhone首发3nm自研芯片,结果“华为发布会”冲上热搜第一…

    明敏 丰色 西风 发自 凹非寺量子位 公众号 QbitAI 就离谱 苹果发iPhone 15 结果发着发着 华为发布会 冲上了热搜第一 哪怕是iPhone 15全系告别11年闪电接口改用USB C 经典静音键从Pro系列消失 这些库克 违背
  • Android性能优化—内存优化

    一 App内存组成以及管理 Android 给每个 App 分配一个 VM 让App运行在 dalvik 上 这样即使 App 崩溃也不会影响到系统 系统给 VM 分配了一定的内存大小 App 可以申请使用的内存大小不能超过此硬性逻辑限制