深入研究Android内存
Android应用程序开发时,我们始终意识到自己是Android硬件和操作系统的一部分。
从硬件角度来看,我们可以将Android手机分为不同级别。基本上,我们可以将它们分类为适用于低端硬件或API的入门级、中级和高级硬件-API手机。在开发应用程序时,以适用于低级硬件或API的水平进行开发意味着吸引更多的用户。
随着时间的推移,我们倾向于将越来越多的功能和技术集成到我们的应用程序中,这意味着随着时间的推移增加了代码维护工作量,并对硬件内存等提出了更高的要求。
Android操作系统的工作原则是未使用或空闲的内存是浪费的内存。它始终试图以优化的方式利用所有可用空间来满足需求。如果有足够的空间,系统在应用程序关闭或置于后台后仍然保留内存。当用户再次打开应用程序时,它会更快地启动。这就是为什么大多数Android设备只剩下很少的可用空间的原因。
软件需要通过访问来使用内存。通常,这是通过Linux内核和驱动程序实现的。然后,应用程序通过驱动程序提供的接口进行通信。内存可以分为虚拟内存和物理内存。CPU使用Memory Management来管理RAM。
Android操作系统共享RAM页面以适应其在RAM中所需的所有内容。每个应用程序进程在系统启动时分叉Zygote并启动,当通用框架代码加载时。这种方法允许框架源代码在大多数RAM页面之间共享。
Android设备包括RAM-zRam和存储。
- RAM:大小有限。速度最快的内存类型。
- zRam:用于短期情况的RAM部分。所有数据在移入和移出zRam时都会被压缩,并在复制时解压缩。
- 存储:包含持久数据。它包含应用程序、库和平台文件等数据。
如果没有足够的内存,Android操作系统会采取各种预防措施。让我们考虑一个场景,多个应用程序在后台运行。Android操作系统在系统中分阶段激活垃圾收集-kswapd和低内存杀手。
垃圾收集
基本上,它有两个重要任务。
- 查找程序中将来无法访问的对象
- 使系统能够回收不可访问对象使用的资源
操作系统跟踪内存的每个分配区域,并在那里管理操作。ART和Dalvik类似。内存根据对象的大小和持续时间以及其持续时间的长短进行分类和管理。
在第一阶段,对象被转移到Young阶段,然后转移到Old-Permanent阶段。每个对象在内存中都有一个上限。当Generation开始填满时,Android操作系统运行垃圾收集。GC运行时间因活动对象的数量和大小而异。大多数时候,GC由系统管理。有方法可以干预这个过程,但不建议这样做。
Kswapd(内核交换守护进程)
Android操作系统使用内存映射页进行工作。每个页面对应大约4kb。页面可以被归类为缓存、已使用和空闲。页面可以被分类为Clean和Dirty。
- Clean:内存中文件的未修改副本。
- Dirty:内存中文件的修改副本。
未使用的页面是RAM的空闲部分。正在使用的页面指的是活动使用的RAM。
可以删除Clean页面,因为它们可以始终使用数据重新创建。但是,不能删除Dirty页面,否则修改的数据将丢失。
Kswapd是Linux内核的一部分,它将已使用的内存转换为可用内存。当内存达到一定水平时,它会被触发。Kswapd开始回收内存。当有足够的空间可用时,回收过程停止。通过删除页面或将其移动到zRam进行压缩来回收内存。
低内存杀手(Low Memory Killer)
尽管采取了预防措施,但如果无法获得足够的内存空间,就会调用它。系统使用onTrimMemory方法通知应用程序内存即将耗尽,应减少分配。如果还不够,就会激活低内存杀手。它开始根据特定评分系统停止进程。
LMK从表格顶部向下开始终止进程。设备制造商可以更改LMK的行为。让我们在一个完整的场景中看看这些例子。
在低端设备上,当用户逐渐开始使用多个应用程序时,垃圾收集器会在接近应用程序使用的内存限制时开始运行。当Android操作系统达到Kswapd阈值时,页面会被移动到zRam并进行压缩或清理以释放内存空间。在Kswapd无法应付的情况下,低内存杀手会开始终止应用程序。
这种情况对用户来说是不可取和不利的。假设用户正在使用应用程序X。在内存使用率高时,他将其放在后台并切换到应用程序Y。如果在此期间关闭了应用程序X,当他再次打开该应用程序时,他将等待一个明显的时间。这意味着在重要事务存在时,对用户来说是时间和数据的损失。
内存管理
我们的应用程序使用过多内存可能会有很多原因。导致内存泄漏的情况、库的选择、创建过多嵌套或执行大型操作的即时对象等。可以使用Android Studio中的Memory Profiler或Perfetto等监控工具来检测和调试这些问题。
- 内存泄漏:防止垃圾收集检测和回收未使用的对象。因此,它使我们的内存使用率低于100%。
- 库选择:应用程序中使用的库对应用程序有一定的负载,从编译或运行时执行到它们使用的方法。
- 应用程序大小:我们放置在应用程序中的所有内容(例如使用PNG而不是SVG等)都会影响应用程序的大小。减小APK大小可以显著降低内存使用量。
- 基于工厂:在代码复杂度高、存在过多对象或进行大量操作的区域,可以考虑使用工厂结构。
- 对象池:如果应用程序中重复使用相同类型的对象,则可以使用对象池。当对象不使用并将被销毁时,如果将来需要再次使用它,则可以从池中获取,而不是在需要时重新创建。这需要良好的同步和使用。
许多外部因素会影响Android应用程序的内存使用情况。即使在同一设备的不同API级别或不同的应用程序和权限中,使用情况可能会有所不同。为此,应该对系统进行良好的监控和测试。重要的是要注意应用程序的内存和生命周期,并以最大限度减少数据丢失的方式进行开发。