发布时间:2024-03-06 18:01
Android 高手核心知识点笔记(不断更新中)点击查看 PS:各位童鞋不要忘记给我 star 一波哦~~
众所周知 Android 以 Java 、Kotlin 为编程语言,在编译时 Anrdoid 会将代码产出的字节码文件经优化后形成.dex 文件保存于 Apk 中,而 Android 中所有的应用程序都运行在 Android 虚拟机中。那么既然都已 Class 文件为原材料、以虚拟机为运行载体 Android 虚拟机与 JVM 在内存管理方法有什么差异呢?
在前面的文章已经对 JVM 内存管理相关的内容作了介绍,在此我们总结一下 JVM 的特点。
JVM 是一个介于 Java 程序与运行平台之间的一个抽象层,JVM 是依赖具体平台的,不同的平台都有自己的实现。
例如,你有一个 HelloWorld.java 文件,当你想运行他的时候,需要使用 javac 工具将其编译为 Bytecode 字节码文件,这意味着 javac 并不会和其他编译器一样将程序直接编译为机器码。Bytecode 是一个二进制文件,里面的内容都是以8个字节为宽度的,不同的位置都有着特殊的含义。Bytecode 作为平台无关性的中间码可以作为输入材料运行在各种平台的虚拟机上,这就是 Java 当年最响亮的广告语:Write Once and Run Anywhere (一次编写到处运行)的来源。
从上图可以看出,一旦你生成了 .class 文件,你就可以把他运行在各种平台上,并且它会被虚拟机转换为原生机器码。
目前 Android 虚拟机有两种类型:Dalvik 和 ART,他们都是用来运行 Android App 的。在移动端上,所有的资源都是受限制的例如,电池电量、 CPU 运算能力、内存资源。所以必须优化程序让他能在低功率的设备上运行。
从上面的图可以看出,除了最后两步其余都和 JVM 相同。差异在于在 Apk 编译期会使用 Dex 编译器优化 .class 文件,生成 .dex 文件作为 Android VM 的执行代码源头。
对比 JVM 和 Android VM,从架构的角度来看二者最大的差异在于 JVM 的指令是基于栈的,而 Android VM 的指令是基于寄存器的。最直观的效果就是 Android VM 执行程序的效率更高,这是因为寄存器离 CPU 更近、数据传输更快。而且基于栈模型的 JVM 在执行时, CPU 需要处理更多的指令去搬运数据,很明显 Android VM 在执行程序时需要的指令会更少,而且省去了存储部分内存太小(对于 JVM 运行架构的更多内容请阅读《深入理解 Java 虚拟机》第八章 - 虚拟机字节码执行引擎)
Android VM | JVM |
---|---|
基于寄存器模式,运行时速度更快更节省内存 | 基于栈模式 |
使用自己的特有的字节码且输入源为 .dex 文件从 Android 2.2 开始 Dalvik 虚拟机引入了 JIT 及时编译功能 | 执行字节码输入源为 .class 文件拥有 JIT 功能 |
每一个 Application 都有一个单独的 VM | 一个 JVM 实例可以共享个多个 Application |
是 Android 平台特有的,不支持其他平台操作系统 | JVM 支持多平台操作系统 |
每个 Application 都有常量池 | 每个 .class 都有常量池 |
可执行文件是 apk | 可执行文件是 jar |
为什么 Android 放弃现成的 JVM 不用非得自己造虚拟机出来呢?原因有以下几点。
Though JVM is free, it was under GPL license, which is not good for Android as most the Android is under Apache license.
JVM was designed for desktops and it is too heavy for embedded devices.
DVM takes less memory, runs and loads faster compared to JVM.
— The OHIO State University
翻译过来是:
— 俄亥俄州立大学
Android VM 是基于优化过的 Bytecode 字节码文件工作的,它针对移动平台优化。这是因为移动平台相比 PC 具有更少的内存、较低的功耗、效率更低的CPU。
Android OS 使用内存分页和内存映射(Memory-mapped )来管理内存。在 Android OS 运行时不会浪费任何内存空间,它会尝试使用所有的内存。例如,==系统会在应有关闭后将其保存在内存当中,以便用户快速切换应用程序。==因此,通常情况下 Android OS 几乎没有空闲的内存。要在系统进程和许多应用直接正确分配内存,内存管理至关重要。
Android 设备包含是三种不同类型的内存:RAM、zRAM和存储器(CPU 和 GPU 访问同一个 RAM)。
为了在 RAM 中容纳所需要的一切,Android 会尝试跨进程共享 RAM 页面。可以通过以下方式实现:
.odex
文件中进行直接内存映射)、应用资源(通过将资源表格设计为可内存映射的结构以及通过对齐 APK 的 zip 条目)和传统项目元素(如 .so
文件中的原生代码)。为了维持多任务环境的正常运行,Android 会为每个应用的堆大小设置硬性上限。不同设备的确切堆大小上限取决于设备的总体可用 RAM 大小。如果您的应用在达到堆容量上限后尝试分配更多内存,则可能会收到 OutOfMemoryError
。
在某些情况下,例如,为了确定在缓存中保存多少数据比较安全,您可能需要查询系统以确定当前设备上确切可用的堆空间大小。您可以通过调用 ActivityManager#getMemoryClass() 向系统查询此数值。此方法返回一个整数,表示应用堆的可用兆字节数。
同样的 Android 也会将内存分页管理,每个页也是 4k 大小。系统会将页面视为 「可用」和「已使用」两种。可用页面就是未使用的 RAM。已使用的页面就是系统目前正在使用的 RAM,并分为以下类别:
kswapd
删除释放内存(因为文件未经过修改,所以可以再次读取到一模一样的数据,删了也没关系)kswapd
移动到 zRAM 中增加内存空间kswapd
或者通过其他方法将更改写回到存储器,咱家可用空间kswapd
移动到 zRAM/在 zRAM 中进行压缩以增加可用内存Android 系统在面临内存不足时有两种解决策略:内核交换守护进程和低内存终止进程。
内核交换守护进程 kswapd 是 Linux 内核的一部分,用于将已使用的内存转换为可用内存。等设备上的内存不足之处 kswapd 将变化为活跃状态。Linux 内核对于可用内存设有上下阈值,当可用内存下降到下限阈值以下时,kswapd 开始回收内存。当可用内存大于上限阈值的时候,kswapd 停止回收内存。
kswapd
可以删除干净页来回收他们,因为这些页受到存储器的支持且未经过修改。如果某个进程需要使用已经删除的干净页的时候,则系统会将该页面从存储器复制到 RAM。此操作称为“请求分页”。
对于脏页面 kswapd
可以将缓存的私有脏页和匿名脏页移动到 zRAM 进行压缩。这样可以释放 RAM 中可用的内存。如果某个进程尝试处理 zRAM 中的脏页,该页将被解压缩并回到 RAM。如果与压缩页面关联的内存被终止了,则该页面会在 zRAM 中被删除。
很多时候通过 kswapd
内核交换进程释放出来的内存并不够使用,这种情况下,系统会使用 onTrimMemeory()
来通知应用进程内存不足,开发者应该减少内存的使用量。如果这还不够,内核则开始启动低内存守护进程(LMK)杀进程。
LMK 使用一个名为 oom_abj_score
的评分机制来计算优先级,分数高的将优先被杀死。一般情况下,后台应用最先被杀系统进程最后被终止。下表列出了从高到低的 LMK 评分类别。评分最高的类别,即第一行中的项目将最先被终止:
内存资源在手机设备上是非常宝贵的,虽然 Android VM (ART、Dalivk)都有自动内存回收策略(GC),但是这并不意味着我们可以对内存置之不理,开发者仍然需要避免内存泄漏,并在适当的时候释放对象引用(Reference)。
在设备出厂的时候,不同的厂商会根据设备情况制定单个应用的最大可内存限制,通过以下命令行查看:
// dvm 最大可用堆内存:
adb shell getprop | grep dalvik.vm.heapsize
// 单个程序限制最大可用堆内存:
adb shell getprop | grep heapgrowthlimit
超过单个程序限制最大堆内存则会抛出OOM,如果设置了手机开启了 largeHeap ,则可提高到 dvm 最大堆内存才OOM。
目前 Android Stuido 的 profile 工具已经很强大,开发者可以内存性能剖析器直观的查找和诊断内存问题。Android Studio 的官方文档对此介绍已经非常详尽,对此不再赘述,参见:内存性能剖析器。
在上面的一节「内存不足的处理策略」中我们提到 Android 设备在内存不足的时候会使用多种内存回收方式回收内存,或者杀死应用。为了能够感知到系统内存不足的情况并避免系统杀死应用进程,Android 提供了 ComponentCallbacks2 接口。此接口需要在 Activity 中实现,在 Activity 启动后会将 callback 注册进入,并在内存情况发生变化的时候回调 onTrimMemory(int level) 方法。如果要随机检测当前 level 可以使用ActivityManager.getMyMemoryState(RunningAppProcessInfo方法。
import android.content.ComponentCallbacks2
// Other import statements ...
class MainActivity : AppCompatActivity(), ComponentCallbacks2 {
// Other activity code ...
/**
* Release memory when the UI becomes hidden or when system resources become low.
* @param level the memory-related event that was raised.
*/
override fun onTrimMemory(level: Int) {
// Determine which lifecycle or system event was raised.
when (level) {
ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN -> {
/*
Release any UI objects that currently hold memory.
The user interface has moved to the background.
*/
}
ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE,
ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW,
ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL -> {
/*
Release any memory that your app doesn\'t need to run.
The device is running low on memory while the app is running.
The event raised indicates the severity of the memory-related event.
If the event is TRIM_MEMORY_RUNNING_CRITICAL, then the system will
begin killing background processes.
*/
}
ComponentCallbacks2.TRIM_MEMORY_BACKGROUND,
ComponentCallbacks2.TRIM_MEMORY_MODERATE,
ComponentCallbacks2.TRIM_MEMORY_COMPLETE -> {
/*
Release as much memory as the process can.
The app is on the LRU list and the system is running low on memory.
The event raised indicates where the app sits within the LRU list.
If the event is TRIM_MEMORY_COMPLETE, the process will be one of
the first to be terminated.
*/
}
else -> {
/*
Release any non-critical data structures.
The app received an unrecognized memory level value
from the system. Treat this as a generic low-memory message.
*/
}
}
}
}
为了允许多个进程同时运行,Android 针对为每个应用分配的堆大小设置了硬性限制。设备的确切堆大小限制因设备总体可用的 RAM 多少而异。如果您的应用已达到堆容量上限并尝试分配更多内存,系统就会抛出 OutOfMemoryError
。
为了避免用尽内存,您可以查询系统以确定当前设备上可用的堆空间。您可以通过调用 getMemoryInfo()
向系统查询此数值。它将返回一个 ActivityManager.MemoryInfo
对象,其中会提供与设备当前的内存状态有关的信息,包括可用内存、总内存和内存阈值(如果达到此内存级别,系统就会开始终止进程)。ActivityManager.MemoryInfo
对象还会提供一个简单的布尔值lowMemory
,您可以根据此值确定设备是否内存不足。
以下代码段示例演示了如何在应用中使用 getMemoryInfo()
方法。
fun doSomethingMemoryIntensive() {
// Before doing something that requires a lot of memory,
// check to see whether the device is in a low memory state.
if (!getAvailableMemory().lowMemory) {
// Do memory intensive work ...
}
}
// Get a MemoryInfo object for the device\'s current memory status.
private fun getAvailableMemory(): ActivityManager.MemoryInfo {
val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
return ActivityManager.MemoryInfo().also { memoryInfo ->
activityManager.getMemoryInfo(memoryInfo)
}
}
内存管理概览
进程间的内存分配
管理应用内存
Android内存管理分析总结
使用Logcat写入和查看日志
调查 RAM 使用情况
《深入解析Android虚拟机》
《深入理解Android:Java虚拟机ART》