largeHeap属性的作用

本文简单聊聊 Android 中经常用到的 largeHeap 属性。

我的习惯,先看官方文档

Whether your application’s processes should be created with a large Dalvik heap. This applies to all processes created for the application. It only applies to the first application loaded into a process; if you’re using a shared user ID to allow multiple applications to use a process, they all must use this option consistently or they will have unpredictable results.
Most apps should not need this and should instead focus on reducing their overall memory usage for improved performance. Enabling this also does not guarantee a fixed increase in available memory, because some devices are constrained by their total available memory.

简单来说,有两点:

  • 这是用于控制当前应用进程是否可以创建一个更大的 Dalvik heap 的开关。
  • 大部分应用不应该使用这个开关,而是关注如何减少自身内存占用以提升性能。

介绍

在 adb shell 中使用如下命令可以找到 heapgrowthlimitheapsize。另外,你可能还对 heapstartsize 感兴趣。

这是在我的华为 Nova 手机上的测试结果。

1
2
3
4
5
6
7
HWANE:/ $ getprop | grep vm.heap                                                                                                                      
[dalvik.vm.heapgrowthlimit]: [384m] // 受控情况下的极限堆,即 largeHeap=false
[dalvik.vm.heapmaxfree]: [8m]
[dalvik.vm.heapminfree]: [4m]
[dalvik.vm.heapsize]: [512m] // 不受控情况下的极限堆大小,即 largeHeap=true
[dalvik.vm.heapstartsize]: [8m] // 堆分配的初始大小
[dalvik.vm.heaptargetutilization]: [0.75]

如果你是 root 手机,也可以在 system/build.prop 中找到这几个属性值。

android dalvik heap 浅析一文中对这几个参数有很形象的比喻:

分配dalvik heap就好像去食堂打饭,有人饭量大,要吃三碗,有人饭量小,连一碗都吃不完。如果食堂按照三碗的标准来给每个人打饭,那绝对是铺张浪费,所以食堂的策略就是先打一碗,凑合吃,不够了自己再来加,设定堆大小也是一样,先给一个合理值,凑合用,自己不够了再跟系统要。食堂毕竟是做买卖的,如果很多人明显吃不了那么多,硬是一碗接着一碗。为了制止这种不合理的现象,食堂又定了一个策略,一般人就只能吃三碗。但是如果虎背熊腰的大汉确实有需要,可以吃上五碗,超过五碗就不给了(太亏本了)

  • dalvik.vm.heapstartsize - 一开始每人能打几碗饭。不妨为1
  • dalvik.vm.heapgrowthlimit - 一般人最多吃几碗饭。假定为3
  • dalvik.vm.heapsize - 虎背熊腰的大汉最多能吃五碗。假定为5

(可想而知,无论需不需要,中国的 app 通常上来就是5碗)

PackagePaser.parseApplication() 方法解析 APK 文件。如果解析到 largeHeap=true 会在 applicationInfo 中添加 FLAG_LARGE_HEAP 标签。相关逻辑最终会进入到 HeapSource.dvmClearGrowthLimit()

1
2
3
4
5
6
7
8
9
10
11
12
/*
* Removes any growth limits. Allows the user to allocate up to the
* maximum heap size.
*/
void dvmClearGrowthLimit()
{
...
dvmWaitForConcurrentGcToComplete();
gHs->growthLimit = gHs->maximumSize;
...
dvmUnlockHeap();
}

注释说明代码的作用是允许用户应用分配最大的堆内存,也即从 largeHeap=false 调整成 largeHeap=true 的状态。

  • largeHeap=false 时,用户应用允许分配的最大堆内存为 dalvik.vm.heapgrowthlimit 指定的值
  • largeHeap=true 时,用户应用允许分配的最大堆内存为 dalvik.vm.heapsize 指定的值

具体的内存分配过程可以看Heap.cpp 源码

另外,getMemoryClass()getLargeMemoryClass() 分别用于获取 heap 和 largeHeap (单位均为字节):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public int getMemoryClass() {
return staticGetMemoryClass();
}

static public int staticGetMemoryClass() {
// Really brain dead right now -- just take this from the configured
// vm heap size, and assume it is in megabytes and thus ends with "m".
String vmHeapSize = SystemProperties.get("dalvik.vm.heapgrowthlimit", "");
if (vmHeapSize != null && !"".equals(vmHeapSize)) {
return Integer.parseInt(vmHeapSize.substring(0, vmHeapSize.length()-1));
}
return staticGetLargeMemoryClass();
}

public int getLargeMemoryClass() {
return staticGetLargeMemoryClass();
}

static public int staticGetLargeMemoryClass() {
// Really brain dead right now -- just take this from the configured
// vm heap size, and assume it is in megabytes and thus ends with "m".
String vmHeapSize = SystemProperties.get("dalvik.vm.heapsize", "16m");
return Integer.parseInt(vmHeapSize.substring(0, vmHeapSize.length() - 1));
}

这里附带提一下另外几个常用的内存值:

  • ActivityManager.getMemoryClass() - 正常的 heap 大小上限值
  • ActivityManager.getLargeMemoryClass() - 较大的 heap 大小上限值
  • Runtime.totalMemory() - 当前 VM 的 heap 大小
  • Runtime.maxMemory() - 当前 VM 的 heap 上限值,即 getMemoryClass()getLargeMemoryClass() 的返回值
  • Runtime.freeMemory() - 当前 VM 的 heap 中可用内存大小
  • MemoryInfo.availMem - 系统可用内存大小
  • MemoryInfo.lowMemory - 系统是否处于低内存状态
  • MemoryInfo.threshold - 系统 availMem 低于这个值时认为处理 lowMemory 状态
  • MemoryInfo.totalMem - 系统总内存大小

参考