Android进程的内存管理分析

Android开发之——Profiler-内存分析 这里附上上述的技术体系图相关的几十套腾讯、头条、阿里、美团等公司2021年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。 阅读详情

最近在网上看了不少Android内存管理方面的博文,但是文章大多都是就单个方面去介绍内存管理,没有能全局把握,缺乏系统性阐述,而且有些观点有误。

这样对Android内存管理进行局部性介绍,很难使读者建立系统性概念,无法真正理解内存管理,对提高系统优化和系统稳定性分析方面的能力是不够的。

    我结合自己的一些思考和理解,从宏观层面上,对内存管理做一个全局性的介绍,在此与大家交流分享。


首先,回顾一下基础知识,基础知识是理解系统机制的前提和关键:

1、  进程的地址空间

在32位操作系统中,进程的地址空间为0到4GB,

示意图如下:



 

图1

 

这里主要说明一下Stack和Heap:

Stack空间(进栈和出栈)由操作系统控制,其中主要存储函数地址、函数参数、局部变量等等,所以Stack空间不需要很大,一般为几MB大小。

Heap空间的使用由程序员控制,程序员可以使用malloc、new、free、delete等函数调用来操作这片地址空间。Heap为程序完成各种复杂任务提供内存空间,所以空间比较大,一般为几百MB到几GB。正是因为Heap空间由程序员管理,所以容易出现使用不当导致严重问题。

 

2、进程内存空间和RAM之间的关系

进程的内存空间只是虚拟内存(或者叫作逻辑内存),而程序的运行需要的是实实在在的内存,即物理内存(RAM)。在必要时,操作系统会将程序运行中申请的内存(虚拟内存)映射到RAM,让进程能够使用物理内存。

RAM作为进程运行不可或缺的资源,对系统性能和稳定性有着决定性影响。另外,RAM的一部分被操作系统留作他用,比如显存等等,内存映射和显存等都是由操作系统控制,我们也不必过多地关注它,进程所操作的空间都是虚拟地址空间,无法直接操作RAM。

示意图如下:


图2

基础知识介绍到这里,如果读者理解以上知识有障碍,请好好恶补一下基础知识,基础理论知识至关重要。 

 

3、  Android中的进程

(1)   native进程:采用C/C++实现,不包含dalvik实例的进程,/system/bin/目录下面的程序文件运行后都是以native进程形式存在的。如图           3,/system/bin/surfaceflinger、/system/bin/rild、procrank等就是native进程。

 

(2)   java进程:Android中运行于dalvik虚拟机之上的进程。dalvik虚拟机的宿主进程由fork()系统调用创建,所以每一个java进程都是存在于一个native进程中,因此,java进程的内存分配比native进程复杂,因为进程中存在一个虚拟机实例。如图3,Android系统中的应用程序基本都是java进程,如桌面、电话、联系人、状态栏等等。




图3

 

 

4、  Android中进程的堆内存

图1和图4分别介绍了native process和java process的结构,这个是我们程序员需要深刻理解的,进程空间中的heap空间是我们需要重点关注的。heap空间完全由程序员控制,我们使用的malloc、C++ new和java new所申请的空间都是heap空间, C/C++申请的内存空间在native heap中,而java申请的内存空间则在dalvik heap中。




图4

 

5、  Android的 java程序为什么容易出现OOM

这个是因为Android系统对dalvik的vm heapsize作了硬性限制,当java进程申请的java空间超过阈值时,就会抛出OOM异常(这个阈值可以是48M、24M、16M等,视机型而定),可以通过adb shell getprop | grep dalvik.vm.heapgrowthlimit查看此值。

也就是说,程序发生OMM并不表示RAM不足,而是因为程序申请的java heap对象超过了dalvik vm heapgrowthlimit。也就是说,在RAM充足的情况下,也可能发生OOM。

这样的设计似乎有些不合理,但是Google为什么这样做呢?这样设计的目的是为了让Android系统能同时让比较多的进程常驻内存,这样程序启动时就不用每次都重新加载到内存,能够给用户更快的响应。迫使每个应用程序使用较小的内存,移动设备非常有限的RAM就能使比较多的app常驻其中。但是有一些大型应用程序是无法忍受vm heapgrowthlimit的限制的,后面会介绍如何让自己的程序跳出vm heapgrowthlimit的限制。

 

6、  Android如何应对RAM不足

在第5点中提到:java程序发生OMM并不是表示RAM不足,如果RAM真的不足,会发生什么呢?这时Android的memory killer会起作用,当RAM所剩不多时,memory killer会杀死一些优先级比较低的进程来释放物理内存,让高优先级程序得到更多的内存。我们在分析log时,看到的进程被杀的log,如图5,往往就是属于这种情况。




图5

7、  如何查看RAM使用情况

可以使用adb shell cat /proc/meminfo查看RAM使用情况:

MemTotal:        396708 kB

MemFree:           4088 kB

Buffers:           5212 kB

Cached:          211164 kB

SwapCached:           0 kB

Active:          165984 kB

Inactive:        193084 kB

Active(anon):    145444 kB

Inactive(anon):     248 kB

Active(file):     20540 kB

Inactive(file):  192836 kB

Unevictable:       2716 kB

Mlocked:              0 kB

HighTotal:            0 kB

HighFree:             0 kB

LowTotal:        396708 kB

LowFree:           4088 kB

SwapTotal:            0 kB

SwapFree:             0 kB

Dirty:                0 kB

Writeback:            0 kB

AnonPages:       145424 kB

……

……

这里对其中的一些字段进行解释:

MemTotal:可以使用的RAM总和(小于实际RAM,操作系统预留了一部分)

MemFree:未使用的RAM

Cached:缓存(这个也是app可以申请到的内存)

HightTotal:RAM中地址高于860M的物理内存总和,只能被用户空间的程序使用。

HightFree:RAM中地址高于860M的未使用内存

LowTotal:RAM中内核和用户空间程序都可以使用的内存总和(对于512M的RAM: lowTotal= MemTotal)

LowFree: RAM中内核和用户空间程序未使用的内存(对于512M的RAM: lowFree = MemFree)

 

8、  如何查看进程的内存信息

(1)、使用adb shell dumpsys meminfo + packagename/pid:

从图6可以看出,com.example.demo作为java进程有2个heap,native heap和dalvik heap,

native heap size为159508KB,dalvik heap size为46147KB



 

图6 

 

 

(2)、使用adb shell procrank查看进程内存信息

        如图7:



图7

 

解释一些字段的意思:

VSS- Virtual Set Size 虚拟耗用内存(包含共享库占用的内存)

RSS- Resident Set Size 实际使用物理内存(包含共享库占用的内存)

PSS- Proportional Set Size 实际使用的物理内存(比例分配共享库占用的内存)

USS- Unique Set Size 进程独自占用的物理内存(不包含共享库占用的内存)

一般来说内存占用大小有如下规律:VSS >= RSS >= PSS >= USS

 

注意:dumpsys meminfo可以查看native进程和java进程,而procrank只能查看java进程。

 

9、  应用程序如何绕过dalvikvm heapsize的限制

对于一些大型的应用程序(比如游戏),内存使用会比较多,很容易超超出vm heapsize的限制,这时怎么保证程序不会因为OOM而崩溃呢?

(1)、创建子进程

               创建一个新的进程,那么我们就可以把一些对象分配到新进程的heap上了,从而达到一个应用程序使用更多的内存的目的,当然,创建子进程会增加系统开销,而且并不是所有应用程序都适合这样做,视需求而定。

创建子进程的方法:使用android:process标签

(2)、使用jni在native heap上申请空间(推荐使用)

      nativeheap的增长并不受dalvik vm heapsize的限制,从图6可以看出这一点,它的native heap size已经远远超过了dalvik heap size的限制。

只要RAM有剩余空间,程序员可以一直在native heap上申请空间,当然如果 RAM快耗尽,memory killer会杀进程释放RAM。大家使用一些软件时,有时候会闪退,就可能是软件在native层申请了比较多的内存导致的。比如,我就碰到过UC web在浏览内容比较多的网页时闪退,原因就是其native heap增长到比较大的值,占用了大量的RAM,被memory killer杀掉了。

(3)、使用显存(操作系统预留RAM的一部分作为显存)

使用OpenGL textures等API,texture memory不受dalvik vm heapsize限制,这个我没有实践过。再比如Android中的GraphicBufferAllocator申请的内存就是显存。

 

10、Bitmap分配在native heap还是dalvik heap上?

一种流行的观点是这样的:

Bitmap是jni层创建的,所以它应该是分配到native heap上,并且为了解释bitmap容易导致OOM,提出了这样的观点:

              native heap size + dalvik heapsize <= dalvik vm heapsize

详情请看:http://devspirit.blog.163.com/blog/static/16425531520104199512427/

 

但是请大家看看图6,native heap size为159508KB,远远超过dalvik vm heapsize,所以,事实证明以上观点是不正确的。

 

正确的观点:

大家都知道,过多地创建bitmap会导致OOM异常,且native heapsize不受dalvik限制,所以可以得出结论:

Bitmap只能是分配在dalvik heap上的,因为只有这样才能解释bitmap容易导致OOM。

 

但是,有人可能会说,Bitmap确实是使用java native方法创建的啊,为什么会分配到dalvik heap中呢?为了解决这个疑问,我们还是分析一下源码:

涉及的文件:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. framework/base/graphic/java/Android/graphics/BitmapFactory.java  
  2. framework/base/core/jni/Android/graphics/BitmapFactory.cpp  
  3. framework/base/core/jni/Android/graphics/Graphics.cpp  

BitmapFactory.java里面有几个decode***方法用来创建bitmap,最终都会调用:

private staticnative Bitmap nativeDecodeStream(InputStream is, byte[] storage,Rect padding,Options opts);

而nativeDecodeStream()会调用到BitmapFactory.cpp中的deDecode方法,最终会调用到Graphics.cpp的createBitmap方法。

 

我们来看看createBitmap方法的实现:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. jobjectGraphicsJNI::createBitmap(JNIEnv* env, SkBitmap* bitmap, jbyteArray buffer,  
  2.                                   boolisMutable, jbyteArray ninepatch, int density)  
  3. {  
  4.     SkASSERT(bitmap);  
  5.     SkASSERT(bitmap->pixelRef());  
  6.    
  7.     jobject obj = env->NewObject(gBitmap_class, gBitmap_constructorMethodID,  
  8.            static_cast<jint>(reinterpret_cast<uintptr_t>(bitmap)),  
  9.             buffer, isMutable, ninepatch,density);  
  10.     hasException(env); // For the side effectof logging.  
  11.     return obj;  
  12. }  

 

从代码中可以看到bitmap对象是通过env->NewOject( )创建的,到这里疑惑就解开了,bitmap对象是虚拟机创建的,JNIEnv的NewOject方法返回的是java对象,并不是native对象,所以它会分配到dalvik heap中。

 

11、java程序如何才能创建native对象

必须使用jni,而且应该用C语言的malloc或者C++的new关键字。实例代码如下:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. JNIEXPORT void JNICALLJava_com_example_demo_TestMemory_nativeMalloc(JNIEnv *, jobject)  
  2. {  
  3.           
  4.          void * p= malloc(1024*1024*50);  
  5.    
  6.          SLOGD("allocate50M Bytes memory");  
  7.    
  8.          if (p !=NULL)  
  9.          {         
  10.                    //memorywill not used without calling memset()  
  11.                    memset(p,0, 1024*1024*50);  
  12.          }  
  13.          else  
  14.                    SLOGE("mallocfailure.");  
  15.    ….  
  16.    ….  
  17. free(p); //free memory  
  18. }  

或者:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. JNIEXPORT voidJNICALL Java_com_example_demo_TestMemory_nativeMalloc(JNIEnv *, jobject)  
  2. {  
  3.           
  4.          SLOGD("allocate 50M Bytesmemory");  
  5.          char *p = new char[1024 * 1024 * 50];  
  6.          if (p != NULL)  
  7.          {         
  8.                    //memory will not usedwithout calling memset()  
  9.                    memset(p, 1, 1024*1024*50);  
  10.          }  
  11.          else  
  12.                   SLOGE("newobject failure.");  
  13.  ….  
  14. ….  
  15. free(p); //free memory  
  16. }  

这里对代码中的memset做一点说明:

       new或者malloc申请的内存是虚拟内存,申请之后不会立即映射到物理内存,即不会占用RAM,只有调用memset使用内存后,虚拟内存才会真正映射到RAM。


本文旨在让大家对Android内存管理有一个整体性的认识,着重全局性理解,希望对大家有用。

如果对java层内存泄漏感兴趣,可以阅读我的文章 Android内存泄漏分析及调试

android 如何分析应用的内存 (一)——内存总览 获得如下信息PSS:这是一种按照比列来划分内存的统计方法。比如一个共享库占1G。由两个进程共享,那么PSS的计算方式就是,将1G一分为2。分别为512M按照上图,举例如下:Native Head 按照PSS统计方法,占据107179KBPrivate Dirty:其中Private表示只有本进程可访问。Dirty表示还没有写回磁盘。举例如下:如上图第一行表示,分配给本进程的,已经被修改,但未写入磁盘的Native Heap大小为107140KB。 阅读详情

相关推荐

Android内存分析全攻略:指令详解与工具实践

本文系统介绍Android内存分析的核心技术与工具。首先解析内存指标的4层架构(VSS/RSS/PSS/USS)及其应用场景,重点阐述PSS作为最准确内存指标的评估价值。随后详细讲解ADB命令集,包括dumpsys meminfo、堆转储生成与性能监控命令的使用技巧。专业工具部分涵盖Android Profiler、MAT和LeakCanary的实战应用,提供从基础监控到深度分析的全套方案。

学无止境 1206

Android 内存分析(java/native heap内存、虚拟内存、处理器内存 )

通过Byte数组和Bitmap来了解,dalvik java 堆内存、native heap 内存、32位设备3G虚拟内存,手机处理器内存等等。

HeXinGen的博客 9178

5G NR切换测量事件解析:从A1到B2的实战应用指南

本文深入解析5G NR切换测量事件,从A1到B2的实战应用指南。详细介绍了系统内切换事件(A1-A6)和系统间切换事件(B1-B2)的触发条件与配置建议,帮助优化网络性能。通过实际案例分享,展示如何调整参数提升切换成功率和降低乒乓切换率,适用于5G网络优化工程师。

tcp8optimizer的博客 514

内存 分析 android,Android进程的内存管理分析

最近在网上看了不少Android内存管理方面的博文,但是文章大多都是就单个方面去介绍内存管理,没有能全局把握,缺乏系统性阐述,而且有些观点有误。这样对Android内存管理进行局部性介绍,很难使读者建立系统性概念,无法真正理解内存管理,对提高系统优化和系统稳定性分析方面的能力是不够的。我结合自己的一些思考和理解,从宏观层面上,对内存管理做一个全局性的介绍,在此与大家交流分享。首先,回顾一下基础知识...

weixin_36221923的博客 257

OpenCV图像去畸变实战指南:从原理到参数调优(附完整代码与效果对比)

本文详细解析了OpenCV图像去畸变的原理与实战方法,对比了三种核心函数(标准、鱼眼、全景模型)的适用场景与效果。重点提供了完整的代码示例,并深入探讨了alpha、焦距、中心点等关键参数的调优技巧,以帮助开发者有效矫正镜头畸变,提升计算机视觉任务精度。

life6的博客 553

Win10局域网内文件共享+远程桌面控制保姆级教程(含常见问题解决)

本文提供Win10局域网内文件共享和远程桌面控制的详细教程,包括环境检查、共享文件夹设置、远程桌面连接步骤及常见问题解决方案。通过内置功能实现高效办公协作,提升多设备协同工作效率,特别适合需要频繁传输文件或远程控制场景的专业人士。

rose2的博客 779

进程内存Android进程的内存管理分析

最近应用开发的过程中涌现了一个小问题,顺便记录一下原因和方法--进程内存     尊敬原创作者,转载请注明出处:     http://blog.youkuaiyun.com/gemmem/article/details/8920039          最近在网上看了不少Android内存管理方面的博文,但是文章大多都是就单个方面去分析内存管理,没有能全局掌控,缺乏系统性阐述,而且有

blueangle17的专栏 840

IB网络下不同租户业务隔离方案

IB(InfiniBand)网络隔离可以在不同的通信模式下实现,其中包括 IPoIB(IP over InfiniBand)通信模式。在 IPoIB 模式下,InfiniBand 网络可以承载 IP 数据包,使得基于 IP 的应用程序能够在 InfiniBand 网络上进行通信。在这种情况下,IB 网络隔离通常指通过配置 Partition Key(PKey)来实现不同的租户或应用程序之间的网络隔离。然而,IB 网络隔离并不仅限于 IPoIB 模式。

guganly的专栏 1529

转载:Android进程的内存管理分析

2019独角兽企业重金招聘Python工程师标准>>> ...

weixin_34235105的博客 99

Codesys 3.5报警功能配置保姆级教程:从变量定义到确认方式(含REF/ACK详解)

本文详细介绍了Codesys 3.5报警功能的配置流程,从变量定义到确认方式的选择,特别解析了REF和ACK两种确认方式的底层逻辑。通过实战案例和代码示例,帮助工程师快速掌握报警系统的搭建与调试技巧,提升工业自动化项目的可靠性。

weixin_29245767的博客 458

E2B Agent Runtime Sandbox 沙箱技术架构研究

本实现指南基于E2B的技术架构,提供了完整的基于Firecracker和Docker的Agent Runtime Sandbox后端服务实现方案。架构设计:参考E2B的分层架构,实现API层、管理层和虚拟化层Firecracker集成:深度集成Firecracker提供硬件级安全隔离完整功能:支持文件读写、shell执行、浏览器访问等核心功能生产就绪:提供Docker部署、监控、测试和安全配置可扩展性:设计支持水平扩展和高并发场景。

AI天才研究院 751

Android进程的内存管理分析[非常经典]

Android进程的内存管理分析        原文地址:http://blog.youkuaiyun.com/gemmem/article/details/8920039    作者:余龙飞        最近在网上看了不少Android内存管理方面的博文,但是文章大多都是就单个方面去介绍内存管理,没有能全局把握,缺乏系统性阐述,而且有些观点有误。 这样对Android内存管理进行局部性介

Cheers Li 软件测试博客 3385

一张图讲透银行个人外汇业务逻辑和实操

个人外汇业务主要分为结售汇和外币兑换两类,前者满足日常跨境需求,后者因投机属性多数银行已暂停。业务核心是利用汇率波动实现货币兑换,满足支付或投资需求。交易涉及客户分类、主流币种、多种渠道和汇率计算规则,需区分直接/间接标价法判断汇率走势。银行通过"货币兑换"科目核算损益,采用期末折算确认盈亏。监管要求包括5万美元年度额度、合法渠道、真实性原则及大额申报等。随着跨境金融数字化发展,掌握基础逻辑和合规要求是安全参与外汇业务的关键。

银行信息系统应用架构详解 291

Android 进程的内存管理分析

尊重原创作者,转载请注明出处: http://blog.youkuaiyun.com/gemmem/article/details/8920039 最近在网上看了不少Android内存管理方面的博文,但是文章大多都是就单个方面去介绍内存管理,没有能全局把握,缺乏系统性阐述,而且有些观点有误。 这样对Android内存管理进行局部性介绍,很难使读者建立系统性概念,无法真正理解内存管理,对提高系统优化和系统

Mr_LiaBill 1086

踩遍Windows所有坑:Codex CLI桌面端+命令行双环境安装避坑实战

Windows下装Codex CLI的坑,本质上都是Windows本身的路径、环境变量、权限、编码这些历史遗留问题,和工具本身关系不大。只要避开中文路径、正确配置PATH、注意终端环境差异,其实安装非常简单。这套双环境方案配好之后,桌面端适合可视化调试、多会话管理,终端适合嵌入编辑器、脚本调用,一套配置两边通用,体验还是很流畅的。按照上面的流程走,基本不会出大问题,就算出问题也能在排查表里找到对应解法。

专注于Python爬虫开发,分享爬虫技巧、项目实战与反爬经验,使用Scrapy、BeautifulSoup等工具,解决数据抓取难题。 300

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

上一篇: android ListView的类IOS通讯录效果,分类ListView,ListView标题置顶
下一篇: Android Out Of Memory(OOM) 的详细研究
cding1818
博客等级 码龄17年 963粉丝 154原创
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值