4.3.2 流程示意
LargeObjectMapSpace申请的内存,直接通过 Map 映射到虚拟内存,因此对于 32 位环境应用空间可映射内存在 3G 左右,但虚拟机本身会抢先占用 1G+的地址空间用于管理 Java 内存,因此应用侧实际使用范围在 2G 左右,极端情况下调整后的虚拟机内存理论范围将在 512M~2.5G,至于下限为何是 512M?理论上如果发生 OOM 时虚拟机没有任何大对象,这种情况下,则虚拟机可用内存范围将保持不变,因为我们改变的 Java 大对象的内存可用空间;示意图如下:
4.3.3 关键实现
上面介绍了该方案的背景知识和实现思路,接下来就要从技术层面考虑如何去实现了。如果在系统层面,直接从源码层面定制,相关改动会轻松很多,但是对应用侧来说,要想兼容不同 Android 版本,只有一条路可走——通过 InlineHook 代理相关接口,在执行过程中魔改相关参数以达到目的。在解决完接口代理问题之后,接下来还有下面几件事情要解决:
-
虚拟机并没有对外暴露获取 LargeObjectMapSpace 内存的接口,如何才能实时获取当前 Space 已申请的内存大小?
-
如何在合适的时机同步 Heap::num_bytes_allocated_内存统计,以便于让 LargeObjectMapSpace 的内存"脱离"虚拟机的统计?
-
如何"跳过"虚拟机在内存释放过程对内存大小一致性校验的问题?
4.3.3.1 获取 LargeObjectMapSpace 当前内存
针对第一个问题,尽管 LargeObjectSpace 中提供了获取当前内存大小的接口(LargeObjectSpace::GetBytesAllocated),但是这个接口并没有对外暴露,因此需要通过解析 Libart.so 中的"GetBytesAllocated"符号,以 Android Q 为例,该函数签名符号为:_ZN3art2gc5space16LargeObjectSpace17GetBytesAllocatedEv;并在运行过程中动态获取该符号在内存中的地址。
由于 GetBytesAllocated 是非静态函数,因此在实际调用该接口时,需要知道当前对象的实例化对象,然后通过实例化对象调用该接口即可,在这里,我们通过 inlineHook 代理"LargeObjectMapSpace::Alloc"获取 LargeObjectMapSpace 的实例,LargeObjectMapSpace::Alloc 接口部分源码如下:
mirror::Object* LargeObjectMapSpace::Alloc(Thread* self, size_t num_bytes, size_t* bytes_allocated, size_t* usable_size, size_t* bytes_tl_bulk_allocated) {
std::string error_msg;
MemMap mem_map = MemMap::MapAnonymous(“large object space allocation”,
num_bytes,
PROT_READ | PROT_WRITE,
/low_4gb=/ true,
&error_msg);
…
//申请成功后将当前内存占用+ allocation_size
num_bytes_allocated_ += allocation_size ;
total_bytes_allocated_ += allocation_size;
++ num_objects_allocated_ ; //申请成功后将当前内存数量+1
++total_objects_allocated_;
return obj;
}
在获取 LargeObject