名词解释
- VSS- Virtual Set Size 虚拟耗用内存(包含共享库占用的内存)
- RSS- Resident Set Size 实际使用物理内存(包含共享库占用的内存)
- PSS- Proportional Set Size 实际使用的物理内存(比例分配共享库占用的内存)
- USS- Unique Set Size 进程独自占用的物理内存(不包含共享库占用的内存)
一般来说内存占用大小有如下规律:VSS >= RSS >= PSS >= USS
Android 应用的内存组成
- Dalvik 虚拟机代码 -- 共享内存
- 应用框架代码 -- 共享内存
- 应用框架资源-- 共享内存
- 应用框架 so 库 -- 共享内存
- 应用的代码 -- 私有内存
- 应用的资源-- 私有内存
- 应用的 so 库 -- 私有内存
- 堆内存,其他部分 -- 共享/私有
内存组成索引
- Native Heap: Native 代码分配的内存, 虚拟机和 Android 框架本身也会分配
- Dalvik Heap: Java 代码分配的对象
- Dalvik Other: 类的数据结构和索引
- so mmap: Native 代码和常量
- dex mmap: Java 代码和常量
Dalvik 内存组成
- Dalvik-Heap 所有的 Java 实例
- Dalvik-Other 存放类的数据结构及关系
- LinearAlloc 载入类的函数信息, 随 dex 中函数数量的增加而增加(65535)
- Code-Cache jit 编译代码后的缓存,随代码负责度的增大而增大
- Accounting 用于标记和指针
- dalvik-aux-structure 随类及方法数的增大而增大
- dalvik-bitmap 随 dalvik-heap 的增大而增大
mmap
映射 classes.dex 文件。Dalvik 迅疾需要从 dex 文件中加载类信息、字符串常量等, 还需要在调用函数的时候直接从 mmap 内存中读取函数代码(dvm bytecode)执行; 所以该部分内存是程序运行必不可少的。
应用的 dex 会占据较大的空间, 并且随着代码增加, dex 文件变大, 占用的内存也会增加。
zygote 共享内存机制
Dalvik Pss = 私有内存(Private Dirty) + (共享内存 Shared Dirty / 共享进程数)
一个类的内存消耗
Foo f = new Foo();
一、 loadClass 操作,将类信息从 dex 文件加载进内存:
- 读取 .dex mmap 中 class 对应的数据
- 分配 native-heap 和 dalvik-heap 内存创建 class 对象
- 分配 dalvik-LinearAlloc 存放 class 数据
- 分配 dalvik-aux-structure 存放 class 数据
二、 new instance 操作, 创建对象实例:
- 执行 .dex mmap 中 <clinit> 和 <init> 代码
- 分配 dalvik-heap 创建 class 对象实例
在这个过程中,可能还会分配 dalvik-bitmap 和 jit-code-cache 内存。 如果 class Foo 引用了其他类, 继续按照同样的逻辑创建被引用的 class。
优化策略
Heap 优化
注意内存碎片
- 尽量不要在循环中创建很多临时变量
- 可以将大型的循环拆散、分段或按需执行
原因:内存分配的最小单位是页面, 通常为 4KB。
- 运行过程中生成很多临时变量
- 批量生成过程中, 由于还有空闲内存,虚拟机没有做垃圾回收
- 完成后进行垃圾回收, 清楚所有的临时变量,留下内存碎片。
因为 DVM 没有内存整理功能,可能一整页的内存被使用的只有很小一部分, 但是计算私有内存还是按照整页 4KB 计算。
Dalvik Other 优化
dex 文件中数据基本是按类名的字母顺序排列的, 这样同包名的类会排在一起。实际执行过程中, 同一个 package 下的类并不会全部同时被调用, 而是和很多其他的 package 下的类进行交互。 mmap 加载了整个页面, 其中可能很多是无用数据。可以通过混淆把互相调用的类改为同一个 package。
精简代码, 无用的代码随时清除。 并不是不运行就不会占用内存了。