我目前正在苦恼于Android平台的一个奇怪行为 -- 位图/Java堆内存限制。根据设备不同,Android限制应用程序开发者只能使用16、24或32 MiB的Java堆空间(或在已Root的手机上可能找到任意随机值)。这可能相对较小,但可以使用以下API测量使用情况:
Runtime rt = Runtime.getRuntime();
long javaBytes = rt.totalMemory() - rt.freeMemory();
long javaLimit = rt.maxMemory();
很容易;现在有个小变化。在Android中,除了极少数例外,位图存储在native堆中,不计入Java堆。Google的某位热心净化开发者认为这是“不好的”,允许开发者“获得超过他们应得的份额”。因此,有一个不错的小代码可以计算由位图和其他资源造成的native内存使用量,并将其与Java堆相加。如果超出限制,就会抛出java.lang.OutOfMemory异常。 痛苦
但没关系。我有很多位图,并不需要所有的都一直存在。我可以"分页"一些暂时不使用的位图:
所以,在第一次尝试中,我进行了代码重构,以便能够在每次加载位图时包装try/catch:
while(true) {
try {
return BitmapFactory.decodeResource(context.getResources(), android_id, bitmapFactoryOptions);
} catch (java.lang.OutOfMemory e) {
// Do some logging
// Now free some space (the code below is a simplified version of the real thing)
Bitmap victim = selectVictim();
victim.recycle();
System.gc(); // REQUIRED; else, weird behavior ensues
}
}
看这里,这是一个漂亮的日志片段,显示我的代码捕获了异常,并回收了一些位图:
E/Epic (23221): OUT_OF_MEMORY(捕获java.lang.OutOfMemory) I/Epic (23221): ArchPlatform [android] .logStats()- I/Epic (23221): LoadedClassCount = 0.00M I/Epic (23221): GlobalAllocSize = 0.00M I/Epic (23221): GlobalFreedSize = 0.02M I/Epic (23221): GlobalExternalAllocSize = 0.00M I/Epic (23221): GlobalExternalFreedSize = 0.00M I/Epic (23221): EpicPixels = 26.6M(这是所有加载的位图中像素数目的4倍) I/Epic (23221): NativeHeapSize = 29.4M I/Epic (23221): NativeHeapAllocSize = 25.2M I/Epic (23221): ThreadAllocSize = 0.00M I/Epic (23221): totalMemory()= 9.1M I/Epic (23221): maxMemory()= 32.0M I/Epic (23221): freeMemory()= 4.4M W/Epic (23221): 回收位图'game_word_puzzle_11_aniframe_005' I/Epic (23221): BITMAP_RECYCLING:回收1个位图,价值1.1M)。 年龄= 294
注意totalMemory-freeMemory只有4.7 MiB,但是由位图占用的近26? MiB的本机内存,我们处于31/32 MiB的范围内,超出了限制。 我仍然有点困惑,因为我所有已加载的位图的运行总和为26.6 MiB,但本机分配大小只有25.2 MiB。所以我数错了什么。 但是这都在大约的范围内,绝对演示了跨池“总和”发生的mem-limit。
我以为我修复了它。 但不,Android并没有这么容易放弃...
以下是我从四台测试设备中的两台设备中获取的信息:
I/dalvikvm-heap(17641): 将目标GC堆从32.687MB压缩到32.000MB D/dalvikvm(17641): GC_FOR_MALLOC释放<1K,空闲41%,占用4684K / 7815K,外部24443K / 24443K,暂停24ms D/dalvikvm(17641): GC_EXTERNAL_ALLOC释放<1K,空闲41% ,占用4684K / 7815K,外部24443K / 24443K,暂停29ms E/dalvikvm-heap(17641): 这个进程的1111200字节的外部分配过大。 E/dalvikvm(17641): 内存不足:堆大小= 7815KB,已分配= 4684KB,位图大小= 24443KB,限制= 32768KB E/dalvikvm(17641): 裁剪信息:占用内存= 7815KB,允许占用内存= 7815KB,裁剪880KB E/GraphicsJNI(17641): VM不允许我们分配1111200字节 I/dalvikvm-heap(17641): 将目标GC堆从32.686MB压缩到32.000MB D/dalvikvm(17641): GC_FOR_MALLOC释放<1K,空闲41%,占用4684K / 7815K,外部24443K / 24443K,暂停17ms I/DEBUG(1505):******** ******** ******** ******** ******** ******** ******** ******** ******** ******** I/DEBUG(1505):构建指纹:'verizon_wwe / htc_mecha / mecha:2.3.4 / GRJ22 / 98797:user / release-keys' I/DEBUG(1505):pid:17641,tid:17641 I/DEBUG(1505):信号11(SIGSEGV),代码1(SEGV_MAPERR),故障地址00000000 I/DEBUG(1505):r0 0055dab8 r1 00000000 r2 00000000 r3 0055dadc I/DEBUG(1505):r4 0055dab8 r5 00000000 r6 00000000 r7 00000000 I/DEBUG(1505):r8 000002b7 r9 00000000 10 00000000 fp 00000384 I/DEBUG(1505):ip 0055dab8 sp befdb0c0 lr 00000000 pc ab14f11c cpsr 60000010 I/DEBUG(1505):d0 414000003f800000 d1 2073646565637834 I/DEBUG(1505):d2 4de4b8bc426fb934 d3 42c80000007a1f34 I/DEBUG(1505):d4 00000008004930e0 d5 0000000000000000 I/DEBUG(1505):d6 0000000000000000 d7 4080000080000000 I/DEBUG(1505):d8 0000025843e7c000 d9 c0c0000040c00000 I/DEBUG(1505):d10 40c0000040c00000 d11 0000000000000000 I/DEBUG(1505):d12 0000000000000000 d13 0000000000000000 I/DEBUG(1505):d14 0000000000000000 d15 0000000000000000 I/DEBUG(1505):d16 afd4242840704ab8 d17 0000000000000000 I/DEBUG(1505):d18 0000000000000000 d19 0000000000000000 I/DEBUG(1505):d20 0000000000000000 d21 0000000000000000 I/DEBUG(1505):d22 0000000000000000 d23 0000000000000000 I/DEBUG(1505):d24 0000000000000000 d25 000000000000本地崩溃,segfault错误(sig11)。根据定义,segfault错误始终是一个bug。这显然是 Android 在本地代码中处理GC和/或内存限制检查时的一个bug。但是,我的应用程序仍会崩溃,导致差评、退货和销售额下降。因此,我必须自己计算限制。除了我在这里挣扎。我尝试自己添加像素(EpicPixels),但我仍然偶尔遇到内存崩溃,所以我在低估某些东西。我试图将javaBytes(总数-空闲)添加到NativeHeapAllocSize中,但这会偶尔导致我的应用程序变得“厌食”,不断释放位图,直到没有任何东西可以清除。
1.有人知道计算内存限制并触发 java.lang.OutOfMemory 的确切方法吗? 2.是否有其他人遇到过这个问题并解决了它?你有什么经验? 3.有人知道是哪个 Google 员工想出了这个方案,以便我可以为破坏了我 ~40 小时的生活而打他一拳吗?开个玩笑。
答案:限制为 NativeHeapAllocSize < maxMemory();但是,由于内存碎片化,Android在实际限制之前就会崩溃。因此,您必须将自己限制在实际限制的略低值之下。这个“安全因子”取决于应用程序,但对大多数人来说,几个MiB似乎都可以工作。(我只能说,我被这种行为的破坏深深震惊了)