如何解决安卓系统吃内存、侵占ram的问题?

2024-06-15 14:32

本文主要是介绍如何解决安卓系统吃内存、侵占ram的问题?,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

我发现安卓吃内存的问题确实存在。有图为证。


搞过 android 底层的应该都懂得那部分内存是哪来的。是的,那部分是 Linux 程序占用的内存,包含内核,驱动,C 应用程序框架,C++ API 等等所有不属于 Java 程序的进程。要想看到这些进程,shell 进去 ps 一下就好了。想 kill 的可以 kill ,因为这都是 Linux。
从某种程度上说,你可以把 android 理解为 Linux 之上的一个应用程序,那部分你认为吃掉的内存恰恰是 android 管理不了的部分,一般是由做 ROM 的人通过配置系统启动脚本去控制那些C程序启动的。

Android 是一款基于 Linux 内核,面向移动终端的操作系统。为适应其作为移动平台操作系统的特殊需要,谷歌对其做了特别的设计与优化,使得其进程调度与资源管理与其他平台的 Linux 有明显的区别。主要包含下面几个层次:

  1. Application Framework

    Application Framework 将整个操作系统分隔成两个部分。对应用开发者而言,所有 APP 都是运行在 Application Framework 之上,而并不需要关心系统底层的情况。Application Framework 层为应用开发者提供了丰富的应用编程接口,如 Activity Manager,Content Provider,Notification Manager,以及各种窗口 Widget 资源等。在 Application Framework 层,Activity 是一个 APP 最基本的组成部分。一般每个 Activity 对应于屏幕上的一个视图(或者说一屏),一个 APP 可以有一个或者多个 Activity。应用程序被打包成 .apk 格式的文件,由 Dalvik VM 解释执行。

  2. Dalvik VM

    Dalvik 虚拟机采用寄存器架构,而不是 JVM 的栈结构。Java 程序编译后的 .class 文件并不能在 Dalvik 中解释执行。因此 Google 提供了一个 dx 工具,用于将 .class 文件转换成 Dalivk 能够识别的 .dex 格式。具体 Dalvik VM 的细节不是本文重点,以下不再讨论。

  3. Linux kernel

    由上所述,所有的 APP 都是由 Java 代码编写并在 Dalvik VM 中得到解释执行。在 Android 操作系统中,每个 Dalvik VM 的每个 Instance 都对应于 Linux 内核中的一个进程。可以使用 adb shell 工具查看系统中的当前进程。如下图所示,Android2.3.3 启动后内核中的进程列表。


Android 内存回收原则

下面将从 Application Framework 和 Linux kernel 两个层次分析 Android 操作系统的资源管理机制。

Android 之所以采用特殊的资源管理机制,原因在于其设计之初就是面向移动终端,所有可用的内存仅限于系统 RAM,必须针对这种限制设计相应的优化方案。当 Android 应用程序退出时,并不清理其所占用的内存,Linux 内核进程也相应的继续存在,所谓“退出但不关闭”。从而使得用户调用程序时能够在第一时间得到响应。当系统内存不足时,系统将激活内存回收过程。为了不因内存回收影响用户体验(如杀死当前的活动进程),Android 基于进程中运行的组件及其状态规定了默认的五个回收优先级:

IMPORTANCE_FOREGROUND:

IMPORTANCE_VISIBLE:

IMPORTANCE_SERVICE:

IMPORTANCE_BACKGROUND:

IMPORTANCE_EMPTY:

这几种优先级的回收顺序是 Empty process、Background process、Service process、Visible process、Foreground process。关于划分原则参见 http://developer.android.com/guide/topics/fundamentals/processes-and-threads.html文件中。

ActivityManagerService 集中管理所有进程的内存资源分配。所有进程需要申请或释放内存之前必须调用 ActivityManagerService 对象,获得其“许可”之后才能进行下一步操作,或者 ActivityManagerService 将直接“代劳”。类 ActivityManagerService 中涉及到内存回收的几个重要的成员方法如下:trimApplications(),updateOomAdjLocked(),activityIdleInternal() 。这几个成员方法主要负责 Android 默认的内存回收机制,若 Linux 内核中的内存回收机制没有被禁用,则跳过默认回收。


默认回收过程

Android 操作系统中的内存回收可分为两个层次,即默认内存回收与内核级内存回收,本章重点对默认内存回收机制进行研究,Linux 内核层次的内存回收机制将在下一张介绍。本章所有代码可参见 ActivityManagerService.java。

回收动作入口:activityIdleInternal()

Android 系统中内存回收的触发点大致可分为三种情况。第一,用户程序调用 StartActivity(), 使当前活动的 Activity 被覆盖;第二,用户按 back 键,退出当前应用程序;第三,启动一个新的应用程序。这些能够触发内存回收的事件最终调用的函数接口就是 activityIdleInternal()。当 ActivityManagerService 接收到异步消息 IDLE_TIMEOUT_MSG 或者 IDLE_NOW_MSG 时,activityIdleInternal() 将会被调用。代码如下:


清单 1. IDLE_NOW_MSG 的处理方式
case IDLE_NOW_MSG:{ IBinder token = (Ibinder)msg.obj; activityIdle(token, null); } break;

清单 2. IDLE_TIMEOUT_MSG 的处理方式
case IDLE_TIMEOUT_MSG: { if (mDidDexOpt) { mDidDexOpt = false; Message nmsg = mHandler.obtainMessage(IDLE_TIMEOUT_MSG); nmsg.obj = msg.obj; mHandler.sendMessageDelayed(nmsg, IDLE_TIMEOUT); return; } IBinder token = (IBinder)msg.obj; Slog.w(TAG, "Activity idle timeout for " + token); activityIdleInternal(token, true, null); } break;

IDLE_NOW_MSG 由 Activity 的切换以及 Activiy 焦点的改变等事件引发,IDLE_TIMEOUT_MSG 在 Activity 启动超时的情况下引发,一般这个超时时间设为 10s,如果 10s 之内一个 Activity 依然没有成功启动,那么将发送异步消息 IDLE_TIMEOUT_MSG 进行资源回收。activityIdleInternal() 的主要任务是改变系统中 Activity 的状态信息,并将其添加到不同状态列表中。其主要工作如下:

首先,调用 scheduleAppGcsLocked() 方法通知所有进行中的任务进行垃圾回收。scheduleAppGcsLocked() 将进行调度 JVM 的 garbage collect,回收一部分内存空间,这里仅仅是通知每个进程自行进程垃圾检查并调度回收时间,而非同步回收。然后,取出 mStoppingActivities 和 mFinishigActivities 列表中的所有内容,暂存在临时变量中。这两个列表分别存储了当前状态为 stop 和 finishi 的 activity 对象。对于 stop 列表,如果其中的 activity 的 finish 状态为 true,判断是不是要立即停止,如果要立即停止则调用 destroyActivityLocked() 通知目标进程调用 onDestroy() 方法,否则,先调用 resumeTopActivity() 运行下一个 Activity。如果 finish 状态为 false,则调用 stopActivityLocked() 通知客户进程停止该 Activity,这种情况一般发生在调用 startActivity() 后。对于 finish 列表,直接调用 destroyActivityLocked() 通知客户进程销毁目标 Activity。

这里的 destroyActivityLocked 等函数并没有真正意义上改变内存的使用,只是将其状态改变为“允许回收”,真正的回收在下面即将调用的 trimApplications() 函数中。

回收过程函数 trimApplications()

trimApplications() 函数的结构如下 :

 private final void trimApplications() { synchronized (this) { // First remove any unused application processes whose package // has been removed. for (i=mRemovedProcesses.size()-1; i>=0; i--) { (1)//kill process; } if (!updateOomAdjLocked()) { (2)//do something default } // Finally, if there are too many activities now running, try to // finish as many as we can to get back down to the limit. (3)do something } }

清单 3 中的三个标序号的位置分别负责如下工作:

(1)当程序执行到 trimApplications() 之后,首先检查 mRemovedProcesses 列表中的进程。mRemovedProcesses 列表中主要包含了 crash 的进程、5 秒内没有响应并被用户选在强制关闭的进程、以及应用开发这调用 killBackgroundProcess 想要杀死的进程。调用 Process.killProcess 将所有此类进程全部杀死。

(2)调用 updateOomAdjLocked() 函数,若成功返回,说明 Linux 内核支持 setOomAdj() 接口,updateOomAdjLocked 将修改 adj 的值并通知 linux 内核,内核根据 adj 值以及内存使用情况动态管理进程资源(lowmemorykiller 和 oom_killer)。若 updateOomAdjLocked() 返回为假,则表示当前系统不支持 setOomAdj() 接口,将在本地进行默认的资源回收。

(3)最后,如果当前依然运行了过多的 Activity,对多余的 Activity 进行回收。 trimApplications() 的大多数的代码都在处理 Oom_killer 不存在情况下的默认资源回收,下面对其默认回收过程(即代码清单中标记(2)的位置)进行进一步分析。其回收过程可大致描述如下。

 

步骤一,获取当前所有运行的进程 mLruProcesses,mLruProcesses 中的排序规则是按最近使用时间。对 mLruProcesses 中不能被关闭的进程进行计数,这些不能被关闭的进程包括运行 service 的进程,运行 broadcast receiver 的进程等,见如下代码。

清单 4. 计数不能被关闭的进程
if (app.persistent || app.services.size() != 0 || app.curReceiver != null || app.persistentActivities > 0) { // Don't count processes holding services against our // maximum process count. numServiceProcs++; }

步骤二, 设当前最大运行进程数 curMaxProcs = curMaxProcs + numServiceProcs(即默认最大进程数与运行 Service 的进程数之和),如果当前进程的数量 mRemovedProcesses.size() 大于这个值,则遍历所有当前运行的进程,杀死符合条件的那些进程并释放内存。清理过程见清单 5(部分代码省略)。从清单 5 的代码中可以看出,进程被杀死的条件是:

  • 必须是非 persistent 进程,即非系统进程;
  • 必须是空进程,即进程中没有任何 activity 存在。如果杀死存在 Activity 的进程,有可能关闭用户正在使用的程序,或者使应用程序恢复的时延变大,从而影响用户体验;
  • 必须无 broadcast receiver。运行 broadcast receiver 一般都在等待一个事件的发生,用户并不希望此类程序被系统强制关闭;
  • 进程中 service 的数量必须为 0。存在 service 的进程很有可能在为一个或者多个程序提供某种服务,如 GPS 定位服务。杀死此类进程将使其他进程无法正常服务。

以上条件缺一不可。

清单 5. 清理过程
if (!app.persistent && app.activities.size() == 0 && app.curReceiver == null && app.services.size() == 0) { if (app.pid > 0 && app.pid != MY_PID) { Process.killProcess(app.pid); } else { try { app.thread.scheduleExit(); } catch (Exception e) { // Ignore exceptions. } } // todo: For now we assume the application is not buggy // or evil, and will quit as a result of our request. // Eventually we need to drive this off of the death // notification, and kill the process if it takes too long. cleanUpApplicationRecordLocked(app, false, i); i--; }
步骤三,再次检查当前运行的进程,如果 mRemovedProcesses.size() 仍然大于 curMaxProcs,则放宽条件再次进行回收。判断条件见代码清单 6(部分代码省略)。下面代码中,布尔变量 canQuit 的值为真时,那么这个进程可以被回收。canQuit 的取值分两个步骤,首先是根据进程的属性赋值。 1. 必须是非 persistent 进程,即非系统进程;2. 必须无 broadcast receiver;3. 进程中 service 的数量必须为 0;4. persistent 类型的 activity 数量为 0。与步骤二唯一的不同在第 4 条,这里不要求进程是空进程,只要进程中没有 persistent 类型的 Activity 就可以(Activity 是否是 persistent 类型在开发阶段指定)。这些条件都满足时,再检查进程中每个 Activity 的属性,当该进程中所有的 Activity 都还必须满足三个条件:Activity 的状态已经保存,当前处在不可见状态并且 Activity 已经 Stop。这时杀掉进程只会降低下次调用程序时的加载速度,下次启动时将恢复到关闭之前的状态,并不会在用户体验上造成致命的影响,所以,canQuit 置位为真。这种情况与步骤二的回收方式也有所不同,由于进程中 Activity 的数量不是 0,下一步需要对每个 activity 执行 destroyActivityLocked() 销毁,最后才杀死进程。
清单 6. 执行 destroyActivityLocked() 销毁
boolean canQuit = !app.persistent && app.curReceiver == null && app.services.size() == 0 && app.persistentActivities == 0; int NUMA = app.activities.size(); for (j=0; j<NUMA && canQuit; j++) { HistoryRecord r = (HistoryRecord)app.activities.get(j); canQuit = (r.haveState || !r.stateNotNeeded) && !r.visible && r.stopped; } if (canQuit) { // Finish all of the activities, and then the app itself. for (j=0; j<NUMA; j++) { HistoryRecord r = (HistoryRecord)app.activities.get(j); if (!r.finishing) { destroyActivityLocked(r, false); } r.resultTo = null; } if (app.pid > 0 && app.pid != MY_PID) { Process.killProcess(app.pid); } cleanUpApplicationRecordLocked(app, false, i); i--; //dump(); }

步骤四,上面 3 个过程都是针对整个 process 进行的资源回收。在以上过程执行完毕之后,将在更小的粒度上对 Activity 的资源进行回收。与上面所述类似,列表 mLRUActivities 存储了当前所有运行中的 Activity,排序规则同样为最少访问原则。mLRUActivities.size() 返回系统中运行的 Activity 的数量,当其大于 MAX_ACTIVITIES(MAX_ACTIVITIES 是一个常量,一般值为 20,代表系统中最大允许同时存在的 Activity)时。将回收部分满足条件的 Activity 以减少内存的使用。回收条件代码清单 7 所示:

//Finally, if there are too many activities now running, try to // finish as many as we can to get back down to the limit. for (   i=0; i<mLRUActivities.size() && mLRUActivities.size()  > curMaxActivities; i++) { final HistoryRecord r = (HistoryRecord)mLRUActivities.get(i); // We can finish this one if we have its icicle saved and // it is not persistent. if ((r.haveState || !r.stateNotNeeded) && !r.visible && r.stopped && !r.persistent && !r.finishing) { final int origSize = mLRUActivities.size(); destroyActivityLocked(r, true); if (origSize  > mLRUActivities.size()) { i--; } } }
这里回收的只是 Activity 的内存资源,并不会杀死进程,也不会影响进程的运行。当进程需要调用被杀掉的 Activity 时,可以从保存的状态中回复,当然可能需要相对长一点的时延。


Linux 内核中的内存回收

lowmemorykiller

上面提到,trimApplications() 函数中会执行一个叫做 updateOomAdjLocked() 的函数,如果返回 false,则执行默认回收,若返回 true 则不执行默认内存回收。updateOomAdjLocked 将针对每一个进程更新一个名为 adj 的变量,并将其告知 Linux 内核,内核维护一个包含 adj 的数据结构(即进程表),并通过 lowmemorykiller 检查系统内存的使用情况,在内存不足的情况下杀死一些进程并释放内存。下面将对这种 Android Framework 与 Linux 内核相配合的内存回收机制进行研究。

由于 Android 操作系统中的所有应用程序都运行在独立的 Dalvik 虚拟机环境中,Linux 内核无法获知每个进程的运行状态,也就无法为每个进程维护一个合适的 adj 值,因此,Android Application Framework 中必须提供一套机制以动态的更新每个进程的 adj。这就是 updateOomAdjLocked()。

updateOomAdjLocked() 位于 ActivityManagerService 中,其主要作用是为进程选择一个合适的 adj 值,并通知 Linux 内核更新这个值。updateOomAdjLocked 首先调用 computeOomAdjLocked() 初步计算 adj 的值,然后回到 updateOomAdjLocked() 对其值进行进一步修正。估算流程可参见代码。

关于 adj,其定义在 task_struct->signal_struct->adj, 文件 /kernel/include/linux/sched.h 中。实质为进程数据结构中的一个变量,用来表示发生 Out of Memory 时杀死进程的优先级顺序。lowmemorykiller 利用这个变量对进程的重要程度进行判断,并在内存不足时释放部分空间,其实现在文件 /kernel/drivers/staging/android/lowmemorykiller.c 中。lowmemorykiller 定义了两个数组:lowmem_adj 和 lowmem_minfree。其中 lowmen_adj 定义了一系列 adj 键值,而 lowmem_minfree 的每个元素代表一个内存阈值。如下代码中四个阈值分别是 6MB,8MB,16MB 和 64MB,分别代表当内存小于 64MB 时,adj 大于或等于 12 的那些进程将被杀死并回收,内存小于 16MB 时,adj 大于等于 6 的那些进程将被杀死并回收,内存小于 8MB 时,adj 大于等于 1 的那些进程将被杀死并回收,内存小于 6MB 时,adj 大于等于 0 的所有进程将被杀死并回收。内核中的每个进程都持有一个 adj,取值范围 -17 到 15,值越小代表进程的重要性越高,回收优先级越低,其中 -17 代表禁用自动回收。Android 系统中,只有 0-15 被使用

清单 8. 每个进程都持有一个 adj
static int lowmem_adj[6] = { 0, 1, 6, 12, }; static int lowmem_adj_size = 4; static size_t lowmem_minfree[6] = { 3 * 512,      /* 6MB */ 2 * 1024,     /* 8MB */ 4 * 1024,     /* 16MB */ 16 * 1024,    /* 64MB */ }; static int lowmem_minfree_size = 4;

lowmemorykiller 注册一个 lowmem_shrinker,lowmem_shrinker 利用了标准 Linux 内核中的 Cache Shrinker 来实现,当空闲内存页面不足时,内核线程 kswapd 将用已注册的 lowmem_shrinker 来回收内存页面。

清单 9. 用已注册的 lowmem_shrinker 来回收内存页面

static struct shrinker lowmem_shrinker = { .shrink = lowmem_shrink, .seeks = DEFAULT_SEEKS * 16 }; static int __init lowmem_init(void) { task_free_register(&task_nb); register_shrinker(&lowmem_shrinker); return 0; }


lowmem_shrink 的代码在函数 lowmem_shrink 中,下面给出该函数的主要结构。lowmem_shrink 根据上述规则遍历所有进程,选出需要结束的进程,通过发送一个无法忽略的信号 SIGKILL 强制结束这些进程

清单 10. 强制结束进程
static int lowmem_shrink(struct shrinker *s, int nr_to_scan, gfp_t gfp_mask) { for_each_process(p) { //Select processes to be forced  } if (selected) { force_sig(SIGKILL, selected); rem -= selected_tasksize; } else rem = -1; return rem; }

Oom_killer.

如果上述各种方法都无法释放出足够的内存空间,那么当为新的进程分配应用程序时将发生 Out of Memory 异常,OOM_killer 将尽最后的努力杀掉一些进程来释放空间。Android 中的 OOM_killer 继承自标准 Linux 2.6 内核,用于分配内存时 Out of Memory 的处理。Android 并没有对其实现方式进行修改。其位置在 linux/mm/oom_kill.c。 oom_killer 遍历进程,并计算所有进程的 badness 值,选择 badness 最大的那个进程将其杀掉。函数 badness 的声明如下:

unsigned long badness(struct task_struct *p, unsigned long uptime) 函数 select_bad_process 返回将要杀掉的那个进程。

清单 11. 返回将要杀掉的进程
static struct task_struct *select_bad_process(unsigned long *ppoints, struct mem_cgroup *mem) { for_each_process(p) { points = badness(p, uptime.tv_sec); if (points > *ppoints || !chosen) { chosen = p; *ppoints = points; } } return chosen; }

最后,和 lowmemorykiller 一样,通过发送 SIGKILL 结束选中的进程。由于 oom_killer 与标准 Linux 内核并无不同,这里不再详细研究。

这篇关于如何解决安卓系统吃内存、侵占ram的问题?的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1063715

相关文章

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

基于人工智能的图像分类系统

目录 引言项目背景环境准备 硬件要求软件安装与配置系统设计 系统架构关键技术代码示例 数据预处理模型训练模型预测应用场景结论 1. 引言 图像分类是计算机视觉中的一个重要任务,目标是自动识别图像中的对象类别。通过卷积神经网络(CNN)等深度学习技术,我们可以构建高效的图像分类系统,广泛应用于自动驾驶、医疗影像诊断、监控分析等领域。本文将介绍如何构建一个基于人工智能的图像分类系统,包括环境

水位雨量在线监测系统概述及应用介绍

在当今社会,随着科技的飞速发展,各种智能监测系统已成为保障公共安全、促进资源管理和环境保护的重要工具。其中,水位雨量在线监测系统作为自然灾害预警、水资源管理及水利工程运行的关键技术,其重要性不言而喻。 一、水位雨量在线监测系统的基本原理 水位雨量在线监测系统主要由数据采集单元、数据传输网络、数据处理中心及用户终端四大部分构成,形成了一个完整的闭环系统。 数据采集单元:这是系统的“眼睛”,

NameNode内存生产配置

Hadoop2.x 系列,配置 NameNode 内存 NameNode 内存默认 2000m ,如果服务器内存 4G , NameNode 内存可以配置 3g 。在 hadoop-env.sh 文件中配置如下。 HADOOP_NAMENODE_OPTS=-Xmx3072m Hadoop3.x 系列,配置 Nam

好题——hdu2522(小数问题:求1/n的第一个循环节)

好喜欢这题,第一次做小数问题,一开始真心没思路,然后参考了网上的一些资料。 知识点***********************************无限不循环小数即无理数,不能写作两整数之比*****************************(一开始没想到,小学没学好) 此题1/n肯定是一个有限循环小数,了解这些后就能做此题了。 按照除法的机制,用一个函数表示出来就可以了,代码如下

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

嵌入式QT开发:构建高效智能的嵌入式系统

摘要: 本文深入探讨了嵌入式 QT 相关的各个方面。从 QT 框架的基础架构和核心概念出发,详细阐述了其在嵌入式环境中的优势与特点。文中分析了嵌入式 QT 的开发环境搭建过程,包括交叉编译工具链的配置等关键步骤。进一步探讨了嵌入式 QT 的界面设计与开发,涵盖了从基本控件的使用到复杂界面布局的构建。同时也深入研究了信号与槽机制在嵌入式系统中的应用,以及嵌入式 QT 与硬件设备的交互,包括输入输出设

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

安卓链接正常显示,ios#符被转义%23导致链接访问404

原因分析: url中含有特殊字符 中文未编码 都有可能导致URL转换失败,所以需要对url编码处理  如下: guard let allowUrl = webUrl.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else {return} 后面发现当url中有#号时,会被误伤转义为%23,导致链接无法访问

如何解决线上平台抽佣高 线下门店客流少的痛点!

目前,许多传统零售店铺正遭遇客源下降的难题。尽管广告推广能带来一定的客流,但其费用昂贵。鉴于此,众多零售商纷纷选择加入像美团、饿了么和抖音这样的大型在线平台,但这些平台的高佣金率导致了利润的大幅缩水。在这样的市场环境下,商家之间的合作网络逐渐成为一种有效的解决方案,通过资源和客户基础的共享,实现共同的利益增长。 以最近在上海兴起的一个跨行业合作平台为例,该平台融合了环保消费积分系统,在短