Android应用进程内存组成:
- Dalvik虚拟机代码(共享内存)
- 应用框架的代码(共享内存)
- 应用框架的资源(共享内存)
- 应用框架的so库(共享内存)
- 应用的代码(私有内存)
- 应用的资源(私有内存)
- 应用的so库(私有内存)
- 堆内存,其他部分(共享/私有)
Pss —— Proportional Set Size,表示进程实际使用的物理内存,是由私有内存加上按比例分担计算的各进程共享内存得到的值。
内存的主要组成索引
- Native Heap:Native代码分配的内存,虚拟机和Android框架本身也会分配
- Dalvik Heap:Java代码分配的对象
- Dalvik Other:类的数据结构和索引
- so mmap:Native代码和常量
- dex mmap:Java代码和常量
Dalvik内存
Dalvik_Heap
包括dalvik-heap和dalvik-zygote。堆内存,所有的Java对象实例都放在这里。
LinearAlloc
包括dalvik-LinearAlloc。线性分配器,虚拟机存放载入类的函数信息,随着dex里的函数数量而增加。著名的65535个函数的限制就是从这里来的。
Accounting
包括dalvik-aux-structure、dalvik-bitmap、dalvikcard-table。这部分内存主要做标记和指针表使用。dalvik-aux-structure随着类及方法数目而增大,dalvik-bitmap随着dalvik-heap的增大而增大。
Code_Cache
包括dalvik-jit-code-cache。jit编译代码后的缓存,随着代码复杂度的增加变大。
Dalvik Heap内存常见问题:
随着功能的反复执行,Heap内存一直在持续增长。这种情况通常是出现了内存泄漏,这种情况最适合用LeakCanary等泄漏检查工具进行白盒测试分析。
代码执行时出现了频繁的GC,Heap Alloc内存大幅度波动。这种情况通常是分配了许多临时变量或数组,随后又被迅回收,这种情况在确定具体场景后适合使用Heap Viewer/Allocation Tracker等工具来查看具体分配的对象。
每次启动应用后,Heap内存相比以前版本稳定增长。这种情况通常出现在启动后待机或使用某功能后,可能是由新功能及代码改动引入的固定内存增长。这种情况适合获取Heap Dump后进行多版本或功能使用前后的对比,能够迅速找到增长原因。
Heap Alloc变化不大,但进程的Dalvik Heap Pss(Proportional Set Size)内存明显增加。这种情况比较少见,是由于分配了大量小对象造成的内存碎片。
一个类的内存消耗
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。
内存碎片优化
内存分配的最小单位是页面,通常为4KB。但是由于DVM没有内存整理功能,可能一整页的内存被使用的只有很小一部分,但是计算私有内存还是按照整页 4KB 计算。
于是产生了很多内存碎片。
内存碎片产生过程:
- 运行过程中生成很多临时变量。
- 批量生成过程中, 由于还有空闲内存,虚拟机没有做垃圾回收。
- 完成后进行垃圾回收, 清楚所有的临时变量,留下内存碎片。
内存碎片优化策略:
- 尽量不要在循环中创建很多临时变量。
- 可以将大型的循环拆散、分段或者按需执行。
Dalvik Other 优化
在优化内存时,不只有堆内存,还有其他许多类型的内存能够进行分析和优化。
dex文件有很多优化空间。在仔细统计并调整了dex文件的顺序后,往往能够节约1MB以上的mmap内存。
引入SDK库和调用新的系统API时需要考虑成本。有可能一些不常用的功能会导致大量的内存消耗。这时有可能需要多进程方案,将这些影响内存的操作放入临时进程执行。
网友评论