Android内存管理基础
2021/11/10 7:10:15
本文主要是介绍Android内存管理基础,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
- 仅作为个人学习笔记,请小心求证。
欢迎访问:https://blog.csdn.net/weixin_55626853
内存管理概览:https://developer.android.com/topic/performance/memory-overview
内存分配:https://developer.android.com/topic/performance/memory-management
Android Studio Profiler:https://developer.android.com/studio/profile/memory-profiler
dalvik-heap:https://cs.android.com/android/platform/superproject/+/android-11.0.0_r1:frameworks/native/build/phone-xhdpi-2048-dalvik-heap.mk
https://blog.csdn.net/whbing1471/article/details/105523704
Android系统内存页面:https://developer.android.com/topic/performance/memory-management
一、内存管理概览
Android 运行时 (ART) 和 Dalvik 虚拟机使用分页和内存映射来管理内存。
1.1 内存类型
Android 设备包含三种不同类型的内存:RAM、zRAM 和存储器。请注意,CPU 和 GPU 访问同一个 RAM。
- RAM(Random Access Memory,随机存取存储器) 是最快的内存类型,但其大小通常有限。
- zRAM 是用于交换空间的 RAM 分区。所有数据在放入 zRAM 时都会进行压缩,然后在从 zRAM 向外复制时进行解压缩。这部分 RAM 会随着页面进出 zRAM 而增大或缩小。设备制造商可以设置 zRAM 大小上限。
- 存储器中包含所有持久性数据(例如文件系统等),以及为所有应用、库和平台添加的对象代码。在 Android 上,存储器不像在其他 Linux 实现上那样用于交换空间,因为频繁写入会导致这种内存出现损坏,并缩短存储媒介的使用寿命。
1.2 物理内存 虚拟内存
1.2.1 物理内存
物理内存是指真实存在的插在主板内存槽上的内存条的容量,是Android系统中所有进程共享的存储空间;
1.2.2 虚拟内存
当进程申请内存时得到的是虚拟内存,虚拟内存是对物理内存的抽象;每个进程操作的是自己的虚拟内存,并由CPU负责虚拟内存地址与实际物理地址的映射。
需要注意的是,假如进程没有使用这些虚拟内存,那么不会建立虚拟地址与物理内存的映射。例如:
void main() { for (int i= 0; i < 512; i++) { malloc(1024 * 1024); } } // 结果分析:malloc只申请了内存并未使用,因此进程的虚拟内存大小VSZ是539M,而物理内存RSS只有3M。
进程在真正使用内存时,虚拟内存与物理内存才建立映射关系,例如:
vector<void*> mems; void main() { for (int i= 0; i < 512; i++) { void *p = malloc(1024 * 1024); memset(p, 0, 1024 * 1024); mems.push_back(p); } } // 结果分析:用memset对申请的内存执行了写入操作,因此进程的虚拟内存大小VSZ是539M,而物理内存RSS增加到了527M。
1.2.3 虚拟内存的好处
(1)使应用程序不必管理共享内存空间;由于所有进程共享物理内存,所以物理内存常常是不连续的,而对应用程序来说,虚拟内存的内存地址是连续的,由CPU负责虚拟内存地址的映射;
(2)能够在进程之间共享库使用的内存;详见1.2.4;
(3)由于内存隔离而提高了安全性;
(4)通过使用分页或分段技术,可使用比物理内存大小更多的内存。
1.2.4 共享库内存
共享库所占内存:同一个共享库的代码在物理内存中只会存在一份,这块内存会映射到不同进程的虚拟内存中,对各个进程来说,就像是自己私有的内存一样,而对于系统来说,则是节省了内存的资源。
Android系统实现跨进程共享RAM页面的方式有:
- 每个应用进程都从一个名为 Zygote 的现有进程分叉。系统启动并加载通用框架代码和资源(如 Activity 主题背景)时,Zygote 进程随之启动。为启动新的应用进程,系统会分叉 Zygote 进程,然后在新进程中加载并运行应用代码。这种方法使为框架代码和资源分配的大多数 RAM 页面可在所有应用进程之间共享。
- 大多数静态数据会内存映射到一个进程中。这种方法使得数据不仅可以在进程之间共享,还可以在需要时换出。静态数据示例包括:Dalvik 代码(通过将其放入预先链接的
.odex
文件中进行直接内存映射)、应用资源(通过将资源表格设计为可内存映射的结构以及通过对齐 APK 的 zip 条目)和传统项目元素(如.so
文件中的原生代码)。 - 在很多地方,Android 使用明确分配的共享内存区域(通过 ashmem 或 gralloc)在进程间共享同一动态 RAM。例如,窗口 surface 使用在应用和屏幕合成器之间共享的内存,而光标缓冲区则使用在内容提供器和客户端之间共享的内存。
1.3 VSS RSS PSS USS区别
一个进程的内存信息可以用VSS RSS PSS USS来表示,含义如下:
VSS(Virtual Set Size):虚拟内存大小(ps命令用VSZ表示);
RSS(Resident Set Size):常驻内存大小,应用使用的共享和非共享页面的数量;
PSS(Proportional Set Size):按比例分摊的内存大小,应用使用的非共享内存加上共享内存的均匀分摊数量(例如,如果三个进程共享 3MB,则每个进程的 PSS 为 1MB);
USS(Unique Set Size):进程独占用的物理内存(不包含共享库占用的内存);
举例:已知: (1)共享库libTest.so所占内存共100M,被进程A和进程B共同占用; (2)进程A申请了100M内存,但是实际使用了60M内存; 则,进程A的VSS=100M+100M=200M; RSS=60M+100M=150M; PSS=60M+100/2=110M; USS=60M;
所以,如果想要知道所有进程使用了多少内存,那么可以使用PSS 或 RSS。计算 PSS 需要花很长时间,因为系统需要确定共享的页面以及共享页面的进程数量。RSS 不区分共享和非共享页面(因此计算起来更快),更适合跟踪内存分配量的变化。
1.4 Android系统的页面置换
RAM分为多个“页面”。通常,每个页面为 4KB 的内存。
- 干净页:存储器中未经修改的文件副本;
- 脏页:存储器中经过修改的文件副本。
Android系统上,不像在其他 Linux 实现上那样使用ROM存储器用于交换空间,因为频繁写入会导致ROM出现损坏,并缩短存储媒介的使用寿命。
内核交换守护进程 (kswapd
) 是 Linux 内核的一部分,用于将已使用内存转换为可用内存。当设备上的可用内存不足时,该守护进程将变为活动状态。Linux 内核设有可用内存上下限阈值。当可用内存降至下限阈值以下时,kswapd
开始回收内存。当可用内存达到上限阈值时,kswapd
停止回收内存。
Android系统使用zRAM用于交换空间,对于私有脏页可由 kswapd
移动到zRAM/在zRAM中进行压缩以增加可用内存,在应用程序需要使用这部分内存时,再从zRAM中取出/在zRAM中解压。详见:内存页面置换策略
二、内存分析方法
2.1 adb常用内存分析命令
2.1.1 ps命令
ps命令可以列出Android系统中当前所有进程的信息:
>adb shell ps USER PID PPID VSZ RSS WCHAN ADDR S NAME root 1 0 60588 1360 0 0 S init root 2 0 60588 1360 0 0 S [kthreadd] root 144 2 0 0 0 0 S [kswapd0] root 1034 1 4286332 22916 0 0 S zygote64 root 1035 1 1616152 25532 0 0 S zygote system 570 1 11356 1196 0 0 S servicemanager system 1655 958 4786828 277192 0 0 S system_server u0_a1166 9742 1035 1716624 20 0 0 T com.bc.sample root 727 1 9968 2204 0 0 S lmkd >adb shell ps | grep com.bc.sample u0_a1166 9742 1035 1716624 20 0 0 T com.bc.sample
含义解释:
USER:进程所属用户;
PID:进程ID;
PPID:父进程ID;
VSZ:Virtual Memory Size虚拟内存占用大小,单位:kb
RSS:Resident Set Size物理内存占用大小,单位:kb
NAME:进程名;
2.1.2 dumpsys meminfo
dumpsys meminfo命令可以根据进程名(一般是包名)获取进程当前的内存使用情况:
#命令行输入 >adb shell dumpsys meminfo com.bc.sample Applications Memory Usage (in Kilobytes): Uptime: 654784890 Realtime: 2062282674 ** MEMINFO in pid 13705 [com.bc.sample] ** Pss Private Private SwapPss Heap Heap Heap Total Dirty Clean Dirty Size Alloc Free ------ ------ ------ ------ ------ ------ ------ Native Heap 3445 3396 0 50 7680 6140 1539 Dalvik Heap 667 612 0 68 2671 1135 1536 Dalvik Other 360 348 0 0 Stack 48 48 0 0 Ashmem 2 0 0 0 Gfx dev 1664 1576 88 0 Other dev 12 0 12 0 .so mmap 1528 180 0 31 .apk mmap 166 0 28 0 .ttf mmap 58 0 0 0 .dex mmap 3834 4 1744 0 .oat mmap 102 0 0 0 .art mmap 3368 3036 4 7 Other mmap 13 4 0 0 EGL mtrack 18496 18496 0 0 GL mtrack 14964 14964 0 0 Unknown 473 464 0 6 TOTAL 49362 43128 1876 162 10351 7275 3075 App Summary Pss(KB) ------ Java Heap: 3652 Native Heap: 3396 Code: 1956 Stack: 48 Graphics: 35124 Private Other: 828 System: 4358 TOTAL: 49362 TOTAL SWAP PSS: 162 Objects Views: 22 ViewRootImpl: 2 AppContexts: 3 Activities: 1 Assets: 3 AssetManagers: 4 Local Binders: 15 Proxy Binders: 21 Parcel memory: 3 Parcel count: 13 Death Recipients: 2 OpenSSL Sockets: 0 WebViews: 0 Dalvik isLargeHeap: false SQL MEMORY_USED: 0 PAGECACHE_OVERFLOW: 0 MALLOC_SIZE: 0
2.1.3 adb shell cat /proc/meminfo
读取/proc/meminfo文件,可以获取系统整体内存使用情况:
>adb shell cat /proc/meminfo MemTotal: 5826364 kB //内存总大小 MemFree: 85532 kB //空闲内存总大小 MemAvailable: 3540368 kB Buffers: 127840 kB Cached: 3373548 kB SwapCached: 10952 kB Active: 2928196 kB Inactive: 1346956 kB Active(anon): 427512 kB Inactive(anon): 432012 kB Active(file): 2500684 kB Inactive(file): 914944 kB Unevictable: 81280 kB Mlocked: 81280 kB SwapTotal: 2097148 kB //交换空间总大小 SwapFree: 1449580 kB //交换空间空闲大小
2.1.4 procrank
procrank命令可以获取系统中所有进程的VSS、RSS、PSS、USS的大小:
>adb shell procrank PID Vss Rss Pss Uss cmdline 380 2195880K 210712K 111133K 67400K system_server 146 1561656K 63080 47628K 42036K zygote 868 1587984K 69500K 25414K 18480K com.android.launcher 145 2122596K 73552K 23428K 10972K zygote64
2.2 Android Studio Profiler
Android studio自带的profiler工具可以选择MEMORY查看当前内存使用情况,如下所示:
其中列出了所选进程的内存使用情况,这里的java heap、native heap都是指虚拟内存大小:
2.3 代码获取
2.3.1 手机总内存情况MemoryInfo
MemoryInfo内包含的内存信息如下:
public static class MemoryInfo implements Parcelable { public long availMem; public long totalMem; public long threshold; public boolean lowMemory; }
获取MemoryInfo的方式如下:
// Activity中已经实现了获取activityManager,自定义类中通过这种方式获取ActivityManager: ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo(); am.getMemoryInfo(memoryInfo); Log.v(TAG,"手机总内存:" + memoryInfo.totalMem); Log.v(TAG,"手机当前可用物理内存:" + memoryInfo.availMem); Log.v(TAG,"Android系统认为低内存状态的阈值:" + memoryInfo.threshold);
2.3.2 读取/proc/meminfo文件
读取/proc/meminfo文件,同样可获取系统总内存情况,可获取的信息见2.1.3;代码如下:
public static long getRamSwapFree() { // // 系统内存信息文件 String str1 = "/proc/meminfo"; String str2; String[] arrayOfString; long swapFree = 0; InputStreamReader localFileReader = null; BufferedReader localBufferedReader = null; try { localFileReader = new InputStreamReader(new FileInputStream(str1), Charset.forName("utf-8")); localBufferedReader = new BufferedReader(localFileReader, 8192); while ((str2 = localBufferedReader.readLine()) != null) { arrayOfString = str2.split("\s+"); if (arrayOfString.length >= 2) { String memType = arrayOfString[0]; // 下面是个例子,类似地可获取其他更多信息 if (TextUtils.equals(arrayOfString[0], "SwapFree:")) { swapFree = Integer.parseInt(arrayOfString[1]); } } } } catch (Exception e) { e.printStackTrace(); } finally { if (null != localBufferedReader) { try { localBufferedReader.close(); } catch (IOException e) { e.printStackTrace(); } } if (null != localFileReader) { try { localFileReader.close(); } catch (IOException e) { e.printStackTrace(); } } } return swapFree / (1024); }
2.3.3 获取当前进程PSS物理内存
Debug.MemoryInfo memoryInfo = new Debug.MemoryInfo(); Debug.getMemoryInfo(memoryInfo); Log.v(TAG,"总PSS:"+memoryInfo.getTotalPss()); Log.v(TAG,"Java PSS:" + memoryInfo.dalvikPss); Log.v(TAG,"Native PSS:" + memoryInfo.nativePss); Log.v(TAG,"总RSS:"+memoryInfo.getTotalRss()); // 安卓6.0以上还可以使用如下方式: Log.v(TAG,"安卓6.0以上,total-pss:"+memoryInfo.getMemoryStat("summary.total-pss")); Log.v(TAG,"安卓6.0以上,java-heap:"+memoryInfo.getMemoryStat("summary.java-heap")); Log.v(TAG,"安卓6.0以上,native-heap:"+memoryInfo.getMemoryStat("summary.native-heap")); Log.v(TAG,"安卓6.0以上,code:"+memoryInfo.getMemoryStat("summary.code")); Log.v(TAG,"安卓6.0以上,graphics:"+memoryInfo.getMemoryStat("summary.graphics"));
2.3.4 获取当前进程VSS虚拟内存
(1)获取java堆VSS
long javaMaxHeapSize = Runtime.getRuntime().maxMemory(); long javaTotalHeapSize = Runtime.getRuntime().totalMemory(); long javaFreeHeapSize = Runtime.getRuntime().freeMemory(); Log.v(TAG,"java堆最大内存大小:"+javaMaxHeapSize); Log.v(TAG,"当前java堆内存大小:"+javaTotalHeapSize); Log.v(TAG,"当前java堆空闲内存大小:"+javaFreeHeapSize);
- maxMemory是虚拟机启动过程中就确定的,默认值根据RAM大小设定(192M (2G RAM)、192M (4G RAM)、256M (6G RAM),但是也和具体的机型配置相关),如果开启了largeHeap就是512M)。
- totalMemory是当前堆大小,最多能增加到maxMemory。
(2)获取native堆VSS
long nativeHeap = Debug.getNativeHeapSize(); long nativeAllocHeap = Debug.getNativeHeapAllocatedSize(); long nativeFreeSize = Debug.getNativeHeapFreeSize(); Log.v(TAG,"当前native堆内存大小:"+nativeHeap); Log.v(TAG,"当前native堆已分配内存大小:"+nativeAllocHeap); Log.v(TAG,"当前native堆空闲内存大小:"+nativeFreeSize);
- native heap的增长并不受dalvik vm heapsize的限制,只要虚拟内存有剩余空间,应用程序可以一直在native heap上申请空间,见参考。
ps:native层虚拟内存或物理内存不足时,都会发生OOM。
三、低内存状态
3.1 LMK进程
很多时候,kswapd
不能为系统释放足够的内存。在这种情况下,系统会使用 onTrimMemory()
通知应用内存不足,应该减少其分配量。如果这还不够,内核会开始终止进程以释放内存。它会使用低内存终止守护进程 (LMK) 来执行此操作。
LMK 使用一个名为 oom_adj_score
的“内存不足”分值来确定正在运行的进程的优先级,以此决定要终止的进程。最高得分的进程最先被终止。后台应用最先被终止,系统进程最后被终止。下表列出了从高到低的 LMK 评分类别。评分最高的类别,即第一行中的项目将最先被终止(设备制造商可以更改 LMK 的行为。):
以下是上表中各种类别的说明:
- 后台应用:之前运行过且当前不处于活动状态的应用。LMK 将首先从具有最高
oom_adj_score
的应用开始终止后台应用。 - 上一个应用:最近用过的后台应用。上一个应用比后台应用具有更高的优先级(得分更低),因为相比某个后台应用,用户更有可能切换到上一个应用。
- 主屏幕应用:这是启动器应用。终止该应用会使壁纸消失。
- 服务:服务由应用启动,可能包括同步或上传到云端。
- 可觉察的应用:用户可通过某种方式察觉到的非前台应用,例如运行一个显示小界面的搜索进程或听音乐。
- 前台应用:当前正在使用的应用。终止前台应用看起来就像是应用崩溃了,可能会向用户提示设备出了问题。
- 持久性(服务):这些是设备的核心服务,例如电话和 WLAN。
- 系统:系统进程。这些进程被终止后,手机可能看起来即将重新启动。
- 原生:系统使用的极低级别的进程(例如,
kswapd
)。
3.2 onTrimMemory()
上面介绍过,当内存不足时,系统会回调onTrimMemory()来通知应用程序内存不足,onTrimMemory()的定义如下:
public interface ComponentCallbacks2 extends ComponentCallbacks { void onTrimMemory(@TrimMemoryLevel int level); }
对于Application、Activity、Fragment、Service、ContentProvider,源码中已经继承了ComponentCallbacks2接口,如果开发者需要自定义onTrimMemory的更多行为(例如收到内存不足回调时,进行一些内存释放工作),直接重写父类的方法即可;
如果开发者需要在自定义的类中接收ComponentCallbacks2系统回调,可按如下方式注册:
public class ComponentCallbacksDemo implements ComponentCallbacks2{ public ComponentCallbacksDemo() { // 注册ComponentCallbacks回调 getApplicationContext().registerComponentCallbacks(this); } @Override public void onTrimMemory(int level) { if (level >= TRIM_MEMORY_UI_HIDDEN) { // todo do some free memory work } } @Override public void onConfigurationChanged(@NonNull Configuration newConfig) {} @Override public void onLowMemory() {} }
当onTrimMemory(int level)回调时,level表示内存等级,level有以下几种类型:
/** * Level for {@link #onTrimMemory(int)}: the process is nearing the end * of the background LRU list, and if more memory isn't found soon it will * be killed. */ static final int TRIM_MEMORY_COMPLETE = 80; /** * Level for {@link #onTrimMemory(int)}: the process is around the middle * of the background LRU list; freeing memory can help the system keep * other processes running later in the list for better overall performance. */ static final int TRIM_MEMORY_MODERATE = 60; /** * Level for {@link #onTrimMemory(int)}: the process has gone on to the * LRU list. This is a good opportunity to clean up resources that can * efficiently and quickly be re-built if the user returns to the app. */ static final int TRIM_MEMORY_BACKGROUND = 40; /** * Level for {@link #onTrimMemory(int)}: the process had been showing * a user interface, and is no longer doing so. Large allocations with * the UI should be released at this point to allow memory to be better * managed. */ static final int TRIM_MEMORY_UI_HIDDEN = 20; /** * Level for {@link #onTrimMemory(int)}: the process is not an expendable * background process, but the device is running extremely low on memory * and is about to not be able to keep any background processes running. * Your running process should free up as many non-critical resources as it * can to allow that memory to be used elsewhere. The next thing that * will happen after this is {@link #onLowMemory()} called to report that * nothing at all can be kept in the background, a situation that can start * to notably impact the user. */ static final int TRIM_MEMORY_RUNNING_CRITICAL = 15; /** * Level for {@link #onTrimMemory(int)}: the process is not an expendable * background process, but the device is running low on memory. * Your running process should free up unneeded resources to allow that * memory to be used elsewhere. */ static final int TRIM_MEMORY_RUNNING_LOW = 10; /** * Level for {@link #onTrimMemory(int)}: the process is not an expendable * background process, but the device is running moderately low on memory. * Your running process may want to release some unneeded resources for * use elsewhere. */ static final int TRIM_MEMORY_RUNNING_MODERATE = 5;
3.3 onLowMemory()
onTrimMemory是API 14及以上可使用,对于低系统可使用onLowMemory(),其等同于ComponentCallbacks2的当onTrimMemory(TRIM_MEMORY_COMPLETE}回调,对于高系统建议实现nTrimMemory()即可。
public interface ComponentCallbacks { void onConfigurationChanged(@NonNull Configuration newConfig); void onLowMemory(); }
这篇关于Android内存管理基础的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-01-18android.permission.read_media_video
- 2024-01-18android_getaddrinfo failed eai_nodata
- 2024-01-18androidmo
- 2024-01-15Android下三种离屏渲染技术
- 2024-01-09Android 蓝牙使用
- 2024-01-06Android对接华为AI - 文本识别
- 2023-11-15代码安全之代码混淆及加固(Android)
- 2023-11-10简述Android语音播报TTS
- 2023-11-06Android WiFi工具类
- 2023-07-22Android开发未来的出路