Android进程的内存管理分析
尊重原创作者,转载请注明出处:
http://blog.csdn.net/gemmem/article/details/8920039
最近在网上看了不少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中呢?为了解决这个疑问,我们还是分析一下源码:
涉及的文件:
- framework/base/graphic/java/Android/graphics/BitmapFactory.java
- framework/base/core/jni/Android/graphics/BitmapFactory.cpp
- framework/base/core/jni/Android/graphics/Graphics.cpp
framework/base/graphic/java/Android/graphics/BitmapFactory.java
framework/base/core/jni/Android/graphics/BitmapFactory.cpp
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方法的实现:
- jobjectGraphicsJNI::createBitmap(JNIEnv* env, SkBitmap* bitmap, jbyteArray buffer,
- boolisMutable, jbyteArray ninepatch, int density)
- {
- SkASSERT(bitmap);
- SkASSERT(bitmap->pixelRef());
- jobject obj = env->NewObject(gBitmap_class, gBitmap_constructorMethodID,
- static_cast<jint>(reinterpret_cast<uintptr_t>(bitmap)),
- buffer, isMutable, ninepatch,density);
- hasException(env); // For the side effectof logging.
- return obj;
- }
jobjectGraphicsJNI::createBitmap(JNIEnv* env, SkBitmap* bitmap, jbyteArray buffer,
boolisMutable, jbyteArray ninepatch, int density)
{
SkASSERT(bitmap);
SkASSERT(bitmap->pixelRef()); jobject obj = env->NewObject(gBitmap_class, gBitmap_constructorMethodID,
static_cast<jint>(reinterpret_cast<uintptr_t>(bitmap)),
buffer, isMutable, ninepatch,density);
hasException(env); // For the side effectof logging.
return obj;
}
从代码中可以看到bitmap对象是通过env->NewOject( )创建的,到这里疑惑就解开了,bitmap对象是虚拟机创建的,JNIEnv的NewOject方法返回的是java对象,并不是native对象,所以它会分配到dalvik heap中。
11、java程序如何才能创建native对象
必须使用jni,而且应该用C语言的malloc或者C++的new关键字。实例代码如下:
- JNIEXPORT void JNICALLJava_com_example_demo_TestMemory_nativeMalloc(JNIEnv *, jobject)
- {
- void * p= malloc(1024*1024*50);
- SLOGD("allocate50M Bytes memory");
- if (p !=NULL)
- {
- //memorywill not used without calling memset()
- memset(p,0, 1024*1024*50);
- }
- else
- SLOGE("mallocfailure.");
- ….
- ….
- free(p); //free memory
- }
JNIEXPORT void JNICALLJava_com_example_demo_TestMemory_nativeMalloc(JNIEnv *, jobject)
{ void * p= malloc(1024*1024*50); SLOGD("allocate50M Bytes memory"); if (p !=NULL)
{
//memorywill not used without calling memset()
memset(p,0, 1024*1024*50);
}
else
SLOGE("mallocfailure.");
….
….
free(p); //free memory
}
或者:
- JNIEXPORT voidJNICALL Java_com_example_demo_TestMemory_nativeMalloc(JNIEnv *, jobject)
- {
- SLOGD("allocate 50M Bytesmemory");
- char *p = new char[1024 * 1024 * 50];
- if (p != NULL)
- {
- //memory will not usedwithout calling memset()
- memset(p, 1, 1024*1024*50);
- }
- else
- SLOGE("newobject failure.");
- ….
- ….
- free(p); //free memory
- }
JNIEXPORT voidJNICALL Java_com_example_demo_TestMemory_nativeMalloc(JNIEnv *, jobject)
{ SLOGD("allocate 50M Bytesmemory");
char *p = new char[1024 * 1024 * 50];
if (p != NULL)
{
//memory will not usedwithout calling memset()
memset(p, 1, 1024*1024*50);
}
else
SLOGE("newobject failure.");
….
….
free(p); //free memory
}
这里对代码中的memset做一点说明:
new或者malloc申请的内存是虚拟内存,申请之后不会立即映射到物理内存,即不会占用RAM,只有调用memset使用内存后,虚拟内存才会真正映射到RAM。
本文旨在让大家对Android内存管理有一个整体性的认识,着重全局性理解,希望对大家有用。
如果对java层内存泄漏感兴趣,可以阅读我的文章Android内存泄漏分析及调试
Android进程的内存管理分析的更多相关文章
- Android应用的内存管理
管理应用的内存可以分为两个部分内容: 1. 首先需要理解:How Android Manages App Processes and Memory Allocation? 2. 其次需要考虑:我们设计 ...
- Android中的内存管理机制以及正确的使用方式
概述 从操作系统的角度来说,内存就是一块数据存储区域,属于可被操作系统调度的资源.现代多任务(进程)的操作系统中,内存管理尤为重要,操作系统需要为每一个进程合理的分配内存资源,所以可以从两方面来理解操 ...
- Android 内存管理分析(四)
尊重原创作者,转载请注明出处: http://blog.csdn.net/gemmem/article/details/8920039 最近在网上看了不少Android内存管理方面的博文,但是文章大多 ...
- 【转】Android中的内存管理--不错不错,避免使用枚举类型
原文网址:http://android-performance.com/android/2014/02/17/android-manage-memory.html 本文内容翻译自:http://dev ...
- android的低内存管理器【转】
本文转载自:http://blog.csdn.net/haitaoliang/article/details/22092321 版权声明:本文为博主原创文章,未经博主允许不得转载. 安卓应用不用太在意 ...
- Android进阶篇-内存管理
很多时候我们需要考虑Android平台上的内存管理问题,Dalvik VM给每个进程都分配了一定量的可用堆内存,当我们处理一些耗费资源的操作时可能会产生OOM错误(OutOfMemoryError)这 ...
- memcached 内存管理 分析(转)
Memcached是一个高效的分布式内存cache,了解memcached的内存管理机制,便于我们理解memcached,让我们可以针对我们数据特点进行调优,让其更好的为我所用.这里简单谈一下我对me ...
- iOS 内存管理分析
内存分析 静态分析(Analyze) 不运行程序, 直接检测代码中是否有潜在的内存问题(不一定百分百准确, 仅仅是提供建议) 结合实际情况来分析, 是否真的有内存问题 动态分析(Profile == ...
- producer内存管理分析
1 概述 kafka producer调用RecordAccumulator#append来将消息存到本地内存.消息以TopicPartition为key分组存放,每个TopicPartition对应 ...
随机推荐
- 剑指offer——已知二叉树的先序和中序排列,重构二叉树
这是剑指offer中关于二叉树重构的一道题.题目原型为: 输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树.假设输入的前序遍历和中序遍历的结果中都不含重复的数字.例如输入前序遍历序列{1,2, ...
- 使用Ramdisk 加速 Visualstudio 编译调试
一般来说ASP.NET在执行的时候,会先动态编译在目录 C:\Windows\Microsoft.NET\Framework64\版本\Temporary ASP.NET Files 由于每次修改程序 ...
- Bash debug
Debugging bash scripts Bash can help us to find problems in bash scripts in some ways. You don't exp ...
- delphi 7中使用idhttp抓取网页 解决假死现象
在delphi 7中使用idhttp抓取网页,造成窗口无反应的假死状态.通过搜索获得两种方法. 1.写在线程中,但是调用比较麻烦 2.使用delphi 提供的idantifreeze(必须安装indy ...
- 《我是一只IT小小鸟》 读后感
<我是一只IT小小鸟>一只是我想读list中一个本,但是上次去当当买的时候,竟然缺货了...昨天监考,实在无聊,就上网看电子书了,一天就看完了,看得有点仓促,所以理解估计不深. 1.刘帅: ...
- poj2365---求多边形边长总和
#include <stdio.h> #include <stdlib.h> #include<math.h> #define pi acos(-1) struct ...
- umount.nfs device busy day virsh extend diskSpace, attachDisk
KVM中linux虚拟机的硬盘添加方法 最近虚拟机中运行的东西比较多,很多.而刚启动的时候虚拟机分配的磁盘比较少,随着日志还有平时的上传文件的积累,磁盘空间报警了.网上查了下资料,自己也做了下实验.总 ...
- ocx控件获取使用App的窗口句柄
在CXxxCtrl文件中 HWND hAppWnd = NULL; if (m_pInPlaceSite != NULL) m_pInPlaceSite->GetWindow(&hApp ...
- javascript数组去重算法-----5
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- EasyUI中datagrid实现显示、增加、 删除、 修改、 查询操作(后台代码C#)
菜鸟进入,高手请绕道! +++++++++++++++++++++++++++++++++++++++ 一.数据的显示 1新建HtmlPage2.html页面,引入相关文件.如下所示 <scri ...