android 7.1平台上 Chromium test 出现了一项Fail https://cs.chromium.org/chromium/src/content/browser/tracing/memory_instrumentation_browsertest.cc?l=74&rcl=633bf05fdb96303ebfa68eada1adee9d76dea16f
这项测试检测app初始的内存信息,分配65M的array(保存在 unique_ptr里),然后在JS里使用一些(4M JS array);然后检测app的内存信息。最后,reset这个unique_ptr;再检测一下。预期的结果是内存使用回到初始状态。
为了便于分析问题,我写了个native 测试程序,定义全局变量 nique_ptr buffer_.
std::unique_ptr<char[]> buffer_; void PopulateBuffer() { const int64_t kAllocSize = 65 * 1024 * 1024; buffer_ = std::make_unique<char[]>(kAllocSize); buffer_.reset(); }测试结果看到native heap占用的内存,不会释放。
Applications Memory Usage (in Kilobytes): Uptime: 179111 Realtime: 179111 Pss Private Private Swap Heap Heap Heap Total Dirty Clean Dirty Size Alloc Free ------ ------ ------ ------ ------ ------ ------ Native Heap 66628 66628 0 0 0 0 0 Dalvik Heap 0 0 0 0 0 0 0 Stack 20 20 0 0 Other dev 0 0 0 0 .so mmap 193 120 12 0 Other mmap 47 20 16 0 Unknown 124 124 0 0 TOTAL 67012 66912 28 0 0 0 0 App Summary Pss(KB) ------ Java Heap: 0 Native Heap: 66628 Code: 132 Stack: 20 Graphics: 0 Private Other: 160 System: 72 TOTAL: 67012 TOTAL SWAP (KB): 0将buffer_定义成char *类型的全局变量,同样可以看到这种情况。
char* buffer_; void PopulateBuffer() { char* buffer_; const int64_t kAllocSize = 65 * 1024 * 1024; buffer_ = new char[kAllocSize]; delete[] buffer_; buffer_ = nullptr; }但是将buffer_定义成局部变量,则不会出现这种问题。
void PopulateBuffer() { const int64_t kAllocSize = 65 * 1024 * 1024; char* buffer = new char[kAllocSize]; std::memset(buffer, 1, kAllocSize); delete[] buffer; buffer = nullptr; } or void PopulateBuffer() { const int64_t kAllocSize = 65 * 1024 * 1024; std::unique_ptr<char[]> buffer = std::make_unique<char[]>(kAllocSize); buffer.reset(); }这个现象只在android 7.1上存在,在其它的版本里没有。android 5.1以后,jemalloc是libc默认的heap 管理器。
这并不是内存泄露,如果再次申请,内存使用量不会增加。过上一段时间后,这个内存还会返还给系统。但是程序已经明确Free了,内存会被hold住一段时间,在低内存设置上会导致内存的紧张。
为了搞清楚这个问题,大致看了下jemalloc的代码。
Android 7.1 上Decay timer 被下面这个提交设成了1。
https://android.googlesource.com/platform/external/jemalloc/+/08795324eae5f68d211dc5483746af51203dc661^!/
decay timer会影响large object的释放时间,设成1是为了性能问题,因为设成0时,每次free都会purge。decay timer只在android 7.1上被设成了1。
android 8.1上decay timer又被设成了0。
https://android.googlesource.com/platform/external/jemalloc/+/7d7fbe660d1fe68fa412449d6033177319c7da3e^!/
android O上zygote在fork进程时,会通过mallopt将decay timer设成1。所以android O上Java进程在native heap上的表现是和上面问题一样的。native heap上的大块内存会被hold一段时间。
frameworks/base/core/jni/com_android_internal_os_Zygote.cpp
static void PreApplicationInit() { // The child process sets this to indicate it's not the zygote. gMallocLeakZygoteChild = 1; // Set the jemalloc decay time to 1. mallopt(M_DECAY_TIME, 1); }