这篇文章给大家分享的是有关android要如何进行内存管理的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。
Java Heap,也就是 JVM 中的堆区。简单回顾一下 JVM 中运行时数据区域的划分:
橙色区域的方法栈以及程序计数器属于线程私有,主要存储方法中的局部数据。
方法区主要存储常量以及类信息,线程共享。
堆区主要负责存储创建的对象,几乎一切对象的内存都在堆区中分配,同时也是线程共享。
我们在 android 程序中使用如 Object o = new Object() 代码创建的对象都会在堆区中分配一块内存进行存储,具体如何分配由虚拟机解决而不需要我们开发者干预。当一个对象不再使用时, JVM 中具有垃圾回收机制(GC),会自动释放堆区中无用的对象,重新利用内存。当我们请求分配的内存已经超过堆区的内存大小,则会抛出 OOM 异常。
在 android 中,堆区是一个由 JVM 逻辑划分的区域,他并不是真正的物理区域。堆区并不会直接全部映射和他等量大小的物理内存,而是到了需要使用时,才会去建立逻辑地址和物理地址的映射:
这样可以给应用分配足够的逻辑内存大小,同时也不必在启动时一次性分配一大块的物理内存。在相同大小的内存中,可以运行更多的程序。
当堆区进程 GC 之后,释放出来多余的空闲内存,会返还给系统,减少物理内存的占用。但这个过程涉及到比较复杂的系统调用,若释放的内存较为少量,可能得不偿失,则无需返还给系统,在堆区中继续使用即可。
在 GC 过程中,如果一个对象不再使用,但是其所占用的内存无法被释放,导致资源浪费,这种现象称为内存泄漏。内存泄露会导致堆区中的对象越来越多,内存的压力越来越大,甚至出现 OOM 。因此,内存泄露是我们必须要尽量避免的现象。
堆区的内存分配,属于进程内的内存分配,由进程自己管理。下面讲一个应用,系统是如何为其分配内存的。
系统的运行内存,即为我们常说的 RAM ,是应用的运行空间。每个应用必须装入内存中才可以被执行:
我们安装的应用进程都位于硬盘中
当一个应用被执行时,需要装入到 RAM 中才能被执行(zRAM 是为了压缩数据节省空间而设计,后续会讲到)
CPU 与 RAM 交互,读取指令、数据、写入数据等
RAM 的大小为设备的硬件内存大小,是非常宝贵的资源。现代手机常见的运存是6G、8G或者12G,一些专为游戏研发的手机甚至有18G,但同时价格也会跟上去。
Android 采用分页存储的方式把一个进程存储到 RAM 中。分页存储,简单来说就是把内存分割成很多个小块,每个应用占用不同的小块,这些小块也可以称为页:
前面讲到,进程的堆区并不是一次性分配,当需要分配内存时,系统会为其分配空闲的页;当这些页被回收,那么有可能被返还到系统中。
这里的页、块概念涉及到操作系统的分页存储,这里并不打算展开详细讲解,有兴趣的读者可以自行了解:分页存储-维基百科。本文中的“页”与“块”可以不严谨地理解为同个概念,为了帮助理解这里不进行详细地区分。
分配给进程的页可以分为两种类型:干净页、脏页:
干净页:进程从硬盘中读取数据或申请内存之后未进行修改。这种类型的页面在内存不足的时候可以被回收,因为页中存储的数据可通过其他的途径复原。
脏页:进程对页中的数据进行了修改或数据存储。这类页面不能被直接回收,否则会造成数据丢失,必须先进行数据存储。
zRAM,是作为 RAM 中的一个分区,当内存不足时,可以把一些类型的页压缩之后存储在zRAM中,当需要使用的时候再从zRAM中调出。通过压缩来节省应用的空间占用,同时不需要与硬盘进行调度,提高了速度。
这里需要理解的一个点是:内存中的操作速度要远远比硬盘操作快。即使与zRAM的调入和调出需要压缩和解压,其速度也是比与硬盘交互快得多。
前面我们一直强调,移动设备的内存容量是非常有限的,需要我们非常谨慎地去使用它。幸运的是,JVM 和 android 系统早就帮我们想到了这一点。
面对不同的内存压力,android 会有不同的应对策略。从低到高依次是 GC、内核交换守护进程释放内存、低内存终止守护进程杀死进程释放内存;他们的代价也是逐步上升。下面我们依个来介绍一下。
GC 属于 JVM 内部的内存管理机制,他管理的内存区域是堆区。当我们创建的对象越来多,堆区的压力越来越大时,GC 机制就会启动,开始回收堆区中的垃圾对象。
辨别一个对象是否是垃圾,虚拟机采用的是可达性分析法。即从一些确定活跃有用的对象出发,向下分析他的引用链;如果一个对象直接或者间接这些对象所引用,那么他就不是垃圾,否则就是垃圾。这些确定活跃有用的对象称为 GC Roots:
如上图,其中绿色的对象被 GC Roots 直接或间接引用,则不会被回收;灰色的对象没有被引用则被标记为垃圾
GC Roots对象的类型比较常见的是静态变量以及栈中的引用。静态变量比较好理解,他在整个进程的执行期间不会被回收,因此他肯定是有用的。栈,这里指的是 JVM 运行数据区域中的方法栈,也就是局部变量引用,在方法执行期间肯定是活跃的。由于方法栈属于线程私有,因此这里等于活跃线程持有的对象不会被回收。
因此,如果一个对象对于我们的程序不再使用,则必须解除 GC Roots 对其的引用,否则会造成内存泄露。例如,不要把 activity 赋值给一个静态变量,这样会导致界面退出时activity无法被回收。
GC 也并不是直接对整个堆区进行回收,而是将堆区中的对象分成两个部分:新生代、老年代。
刚创建的对象大都会被回收,而在多次回收中存活的对象则后续也很少被回收。新生代中存储的对象主要是刚被创建不久的对象,而老年代则存储着那些在多次 GC 中存活的对象。那么我们可以针对这些不同特性的对象,执行不同的回收算法来提高GC性能:
对于新创建的对象,我们需要更加频繁地对他们进行GC来释放内存,且每次只需要记录需要留下来的对象即可,而不必要去标记其他大量需要被回收的对象,提高性能。
对于熬过很多次GC的对象,则可以以更低的频率对他门进行GC,且每次只需要关注少量需要被回收的对象即可。
具体的垃圾回收算法就不继续展开了,了解到这里就可以。感兴趣的读者可以阅读相关书籍。
单次的垃圾回收速度是很快的,甚至我们都无法感知到。但当内存压力越来越大,垃圾回收的速度跟不上内存分配的速度,此时就会出现内存分配等待 GC 的情况,也就是发生了卡顿。同时,我们无法控制 GC 的时机,JVM 有一套完整的算法来决定什么时候进行 GC。假如在我们滑动界面的时候触发 GC ,那么展示出来的就是出现了掉帧情况。因此,做好内存优化,对于 app 的性能表现非常重要。
GC 是针对于 Java 程序内部进行的优化。对于移动设备来说,RAM 非常宝贵,如何在有限的 RAM 资源上进行分配内存,也是一个非常重要的话题。
我们的应用程序都运行在 RAM 中,当进程不断申请内存分配,RAM 的剩余内存达到一定的阈值时,会启动内核交换守护进程来释放内存以满足资源的分配。
内核交换守护进程,是运行在系统内核的一个进程,他主要的工作时回收干净页、压缩页等操作来释放内存。前面讲到,android 是基于分页存储的操作系统,每个进程都会被存储到一些页中。分页的类型有两种:干净页、脏页:
当内核交换守护进程启动时,他会把干净页回收以释放内存。当进程再次访问干净页时,则需要去硬盘中再次读取。
对于脏页,内核交换守护进程会把他们压缩后放入 zRAM 中。当进程访问脏页时,则需要从zRAM中解压出来。
通过不断回收和压缩分页的方式来释放内存,以满足新的内存请求。使用此方式释放的内存也无法满足新的内存请求时,android 会启动低内存终止守护进程,来终止一些低优先级的进程。
当 RAM 的被占用内存达到一定的阈值,android 会根据进程的优先级,终止部分进程来释放内存。当低内存终止守护进程启动时,说明系统的内存压力已经非常大了,这在一些性能较差的设备中经常出现。
进程的优先级从高到低排序如下,优先级更高的进程会优先被终止:
从上到下依次是:
后台应用:使用过的 app 会被缓存在后台,下一次打开可以更加快速地进行切换。当内存不足时,此类应用会最快被杀死。
上一个应用:例如从微信跳转到浏览器,此时微信就是上一个应用。
主屏幕应用:这是启动器应用,也就是我们的桌面。如果这个进程被kill了,那么返回桌面时会暂时黑屏。
服务:同步服务、上传服务等等
可觉察的应用:例如正在播放的音乐软件,他可以被我们感知到,但是不在前台。
前台应用:当前正在使用的应用,如果这个应用被kill了,需要向用户报崩溃异常,此时的体验是极差的。
持久性(服务):这些是设备的核心服务,例如电话和 WLAN。
系统:系统进程。这些进程被终止后,手机可能即将重新启动,就像手机突然卡死重启。
原生:系统使用的极低级别的进程,例如我们的内核交换守护进程。
当内存不足,会按照上面的规则,从上到下来终止进程,获得内存资源。这也就是为什么在 android 中我们的后台应用一直被杀死。为了避免我们的应用被优化,内存优化就显得非常重要了。
感谢各位的阅读!关于“android要如何进行内存管理”这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,让大家可以学到更多知识,如果觉得文章不错,可以把它分享出去让更多的人看到吧!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。