A strange Problem
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住一段时间,在低内存设置上会导致内存的紧张。
Decay timer of Jemalloc
为了搞清楚这个问题,大致看了下jemalloc的代码。
Android 7.1 上Decay timer 被下面这个提交设成了1。
decay timer会影响large object的释放时间,设成1是为了性能问题,因为设成0时,每次free都会purge。decay timer只在android 7.1上被设成了1。
android 8.1上decay timer又被设成了0。
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);
}