尊重原创做者,转载请注明出处:java
http://blog.csdn.net/gemmem/article/details/8920039android
最近在网上看了很多Android内存管理方面的博文,可是文章大多都是就单个方面去介绍内存管理,没有能全局把握,缺少系统性阐述,并且有些观点有误。程序员
这样对Android内存管理进行局部性介绍,很难使读者创建系统性概念,没法真正理解内存管理,对提升系统优化和系统稳定性分析方面的能力是不够的。web
我结合本身的一些思考和理解,从宏观层面上,对内存管理作一个全局性的介绍,在此与你们交流分享。shell
首先,回顾一下基础知识,基础知识是理解系统机制的前提和关键:缓存
一、 进程的地址空间app
在32位操做系统中,进程的地址空间为0到4GB,ide
示意图以下:函数
图1性能
这里主要说明一下Stack和Heap:
Stack空间(进栈和出栈)由操做系统控制,其中主要存储函数地址、函数参数、局部变量等等,因此Stack空间不须要很大,通常为几MB大小。
Heap空间的使用由程序员控制,程序员可使用malloc、new、free、delete等函数调用来操做这片地址空间。Heap为程序完成各类复杂任务提供内存空间,因此空间比较大,通常为几百MB到几GB。正是由于Heap空间由程序员管理,因此容易出现使用不当致使严重问题。
二、进程内存空间和RAM之间的关系
进程的内存空间只是虚拟内存(或者叫做逻辑内存),而程序的运行须要的是实实在在的内存,即物理内存(RAM)。在必要时,操做系统会将程序运行中申请的内存(虚拟内存)映射到RAM,让进程可以使用物理内存。
RAM做为进程运行不可或缺的资源,对系统性能和稳定性有着决定性影响。另外,RAM的一部分被操做系统留做他用,好比显存等等,内存映射和显存等都是由操做系统控制,咱们也没必要过多地关注它,进程所操做的空间都是虚拟地址空间,没法直接操做RAM。
示意图以下:
图2
基础知识介绍到这里,若是读者理解以上知识有障碍,请好好恶补一下基础知识,基础理论知识相当重要。
三、 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
四、 Android中进程的堆内存
图1和图4分别介绍了native process和java process的结构,这个是咱们程序员须要深入理解的,进程空间中的heap空间是咱们须要重点关注的。heap空间彻底由程序员控制,咱们使用的malloc、C++ new和java new所申请的空间都是heap空间, C/C++申请的内存空间在native heap中,而java申请的内存空间则在dalvik heap中。
图4
五、 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的限制。
六、 Android如何应对RAM不足
在第5点中提到:java程序发生OMM并非表示RAM不足,若是RAM真的不足,会发生什么呢?这时Android的memory killer会起做用,当RAM所剩很少时,memory killer会杀死一些优先级比较低的进程来释放物理内存,让高优先级程序获得更多的内存。咱们在分析log时,看到的进程被杀的log,如图5,每每就是属于这种状况。
图5
七、 如何查看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)
八、 如何查看进程的内存信息
(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进程。
九、 应用程序如何绕过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申请的内存就是显存。
十、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
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; }
从代码中能够看到bitmap对象是经过env->NewOject( )建立的,到这里疑惑就解开了,bitmap对象是虚拟机建立的,JNIEnv的NewOject方法返回的是java对象,并非native对象,因此它会分配到dalvik heap中。
十一、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 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内存泄漏分析及调试