Random Access Memory(RAM)在任何软件开发环境中都是非常重要的资源,但在物理内存通常很有限的移动操作系统上更为重要。尽管Android Runtime (ART)和Dalvik虚拟机扮演了垃圾回收的角色,但这并不意味着我们可以忽略应用的内存分配与释放的时机和位置。我们也需要避免引入Memory Leaks(内存泄漏,通常由于在Gm需要回收一些释放对象时扔持有对象引用在静态成员变量里,在释放一些引用对象在需要回收时)。
本文会解释Android是如何管理应用的内存分配,以及在开发Android应用的时候如何主动的减少内存的使用。
1、Android是如何管理内存的
Android的运行时(ART)和Dalvik虚拟机使用分页和内存映射(mmapping)来管理内存。这意味着,一个应用程序的任何内存修改,不管是通过分配新的对象还是触摸mmapped页,仍驻留在内存中,不能被调出。从应用程序释放内存的唯一方法是释放应用所持有的引用对象,使内存可用于垃圾收集器。有一个例外:没有修改mmapped中的任何文件,如代码,在系统在其他地方需要使用该内存时被换出RAM。
1. Garbage collection(垃圾回收)
一个管理内存的环境,如ART或Dalvik虚拟机,跟踪每一个内存分配。一旦它确定一块存储器不再被使用的程序,它释放回堆,不需要开发者的任何干预。对于托管内存环境中回收未使用的内存的机制被称为Garbage collection(垃圾回收)垃圾回收有两个目标:找到程序里未来不能被访问的对象并回收这些对象所使用的资源。
2.shared memory(共享内存)
为了适应在RAM中需要的一切,Android尝试共享跨进程内存的pages。它可以通过以下方式做到:
- 每一个app的process都是从Zygote(受精卵)的进程中fork出来的。Zygote进程在系统启动并且加载通用的framework的代码与资源(比如ActivityThemes)时开始启动。为了启动一个新的程序进程,系统会fork Zygote进程生成一个新的process,然后在新的process中加载并运行app的代码。这使得大多数的RAM pages被用来分配给framework的代码与资源,并在应用的所有进程中进行共享。
- 大多数static的数据被映射到一个进程中。这种方式可以使数据不仅能够在进程间进行共享,而且使得它能够在需要的时候被paged out。例如下面几种static的数据:
Dalvik code (by placing it in a pre-linked .odex file for direct mmapping
App resources (by designing the resource table to be a structure that can be mmapped and by aligning the zip entries of the APK)
Traditional project elements like native code in .so files.
在很多地方,Android通过分配的共享内存(例如ashmem(匿名共享内存)或者gralloc)来实现跨进程共享内存。例如,window surfaces在app与screen compositor之间使用共享的内存,cursor buffers在content provider与client之间使用共享的内存。
3.分配和回收应用内存
每一个应用进程的Dalvik 都与一个虚拟的内存范围(heapSize)。这定义了一个了了逻辑堆的大小(heapSize),它可以随着需要增长至系统为每个应用设定的大小限制。
heapSize和实际物理上的内存数量是不等的。当检查app的堆时,Android会计算Proportional Set Size(PSS)的值,PSS是实际共享的内存大小,记录了那些和其他进程进行共享的内存。
Dalvik堆和逻辑堆得大小(heapSize)并不吻合,这意味着Android并没有使用碎片处理去关闭空闲区域。Android只有当堆末端有没用的空间时才能收缩逻辑堆大小(heapsize),但系统仍然可以减少堆所占用的物理内存大小。在垃圾回收之后,Dalvik会遍历heap并找出不使用的pages,然后使用madvise把那些pages返回给kernal。因此,成对的allocations与deallocations大块的数据可以使得物理内存能够被正常的回收。然而,回收碎片化的内存则会使得效率低下很多,因为那些碎片化的分配页面也许会被其他地方所共享到。
4. 限制应用的内存
为了维持多任务的功能环境,Android为每一个app都设置了一个硬性的heap size限制。确切的heapsize限制随着设备可用的RAM大小而有差异,如果app的heapSize已经达到了最大限制而尝试分配更多的内存就会引起OutOfMemoryError,
如果想要查询当前设备的heap size限制大小是多少,然后决定cache的大小。可以通过 getMemoryClass()来查询。这个方法会返回一个整数,表明你的app heap size限制是多少。
也可以通过手机目录system>build.prop文件查看
5、切换应用
当用户切换两个应用时,Android会把那些不是前台的应用()放在 least-recently used (LRU)cache里。比如说当用户第一次启动app时,系统就会为他创建一个进程,但是当用户退出应用是,之前创建的进程并没有退出,系统将进程缓存起来,当用户再返回到应用时,系统就会重用这个进程,这样会让应用切换的更快。
如果你的应用有一个被缓存的进程,虽然它没有被使用,但它仍被保留在内存中,这会影响系统的整体性能。当系统开始进入低内存状态时,它会杀掉Lrucache中最近使用最低的进程,系统也会把进程所占用的内存进行释放。
2、应用如何管理内存
一些Android特性、Java类和代码结构倾向于使用更多的内存。我们可以通过提高代码的效率来减少应用程序对内存的使用
-
减少Service的使用
当我们需要启动一个Service进行后来操作时,只有当需要服务执行操作时再启动服务,当服务执行完毕时要及时销毁服务,否在我们在不经意间就会造成内存泄漏。当一个Service已经不需要了却没有销毁是最糟糕的一个内存管理错误。让一个不需要运行的服务运行是最糟糕的一种内存管理错误。
当我们启动一个服务时,系统会倾向于一直保持服务所在的Process,这使得Service所在的Process非常浪费,因为系统没法把Service里所占用的没有用的RAM给其他的Process,这减少了系统能够缓存的进程的数量,使应用间的切换更加低效。它甚至会导致系统内存不稳定,不能够维持正在运行的服务。
一般情况下应该尽量少使用持久性的Service,因为它一直需要占用内存。谷歌推荐我们使用 JobScheduler和 IntentService(当处理完Intent后会自动停止)。
使用高效的数据容器
编程语言提供的一些类在移动设备上用效率并不是很高,例如使用HashMap就会是内存效率很低,因为它每一个映射都需要一个单独的实例。
Android框架包括几个高效的数据容器,包括 SparseArray, SparseBooleanArray
和 LongSparseArray.
例如,SparseArray类更有效,因为他们避免系统的需要而去对key和value(有时会创建两个)进行autobox(自动装箱)。注意代码抽象
开发人员通常使用抽象仅仅作为一个良好的编程实践,因为抽象可以提高代码的灵活性和维护。然而,抽象代价巨大:通常需要大量更多的需要执行的代码,需要更多的时间和更多的RAM代码映射到内存中。如果你的抽象类不提供一个重要的作用,你应该避免他们。使用nano protobufs序列化数据
Protocol buffers是由谷歌设计用于序列化结构化数据的语言中立的,与平台无关的,可扩展的机制,和 XML类似,但是更小,更快,更简单。如果你决定让你的代码使用nano protobufs ,你应该一直使用nano protobufs在你的客户端代码里。常规protobufs生成非常冗长的代码,这可能会导致应用产生多种问题比如说增加了内存的使用,增加APK大小,执行慢。-
避免内存流失
正如前面所说的,垃圾回收机制并不影响app的性能。然而许多耗时很短的垃圾回收events能够迅速吃掉你的帧时间。系统花费在垃圾回收上的时间越多,它去做其它事情的时间就越少,比如渲染或者音频流。通常,内存流失会引起大量的垃圾回收事件,在实践中,内存流失描述了在指定时间内分配的临时对象的数量。
例如,你可能会内分配多个临时对象在一个for循环里,或者在onDraw方法里创建Paint或者是Bitmap。这两种情况,都是快速的产生了大量的对象,这些都能很快消耗内存,迫使出现一个垃圾回收事件。
所以我们需要找到代码流失比较高的地方,这样才能解决问题。推荐使用 Analyze your RAM usage工具找内存流失的地方。
** 移除掉内存占用比较多的资源和库**
我们的代码里可能会有一些吞噬内存的资源和库却不让我们知道。Apk的整体规模包括第三方的库和或者嵌入的资源会影响app内存的占用。你可以从你的代码中删除任何多余的,不必要的或臃肿的组件来提高应用程序的内存消耗。** 减少Apk的大小**
减少APk的整体规模都能够显著的降低app的内存占用。Bitmap的大小,资源,帧动画和第三方库都会影响apk的大小。AndroidStudio和AndroidSDK提供了多种工具来帮助您降低资源和外部依赖的大小。详情可以查看Reduce APK Size.-
谨慎使用依赖注入框架
依赖注入框架如Guice或RoboGuice可以简化你写的代码,并提供一个自适应环境,这是测试和其他配置更改非常有用。然而,依赖框架并不总是为移动设备优化。例如,这些框架往往通过扫描你的代码注解初始化过程。这可需要大量代码不必要地映射到RAM中。系统分配这些映射到clean区让Android可以删除它们,这些只有当这些映射存在很长一段时间后才会发生。。。
如果我们需要注解框架,可以使用 Dagger框架,Dagger没有使用反射扫描应用的代码。Dagger的严格执行意味着它可以在Android应用中使用,而不增加不必要的内存使用情况。 ** 谨慎使用扩展类库**
扩展类库的代码往往不是为移动环境而编写的,当被用在移动客户端上工作时可能比较低效。当您决定使用一个外部库,您可能需要为移动设备优化该库。计划这项工作的前期,并决定在所有使用它之前分析代码大小和内存占用方面的库。
原文链接:
https://developer.android.com/topic/performance/memory.html
https://developer.android.com/topic/performance/memory-overview.html