Layer的内存泄露问题分析报告

Posted YYPapa

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Layer的内存泄露问题分析报告相关的知识,希望对你有一定的参考价值。

【问题描述】

home应用在运行monkey测试6个小时候,Native Heap增长到200MB,怀疑内存泄露。

我们可以动过dumpsys查看Native Heap的大小:

$adb shell  dumpsys meminfo com.miui.home
Applications Memory Usage (kB):
Uptime: 12929068 Realtime: 12929060

** MEMINFO in pid 1393 [com.miui.home] **
                   Pss  Private  Private  Swapped     Heap     Heap     Heap
                 Total    Dirty    Clean    Dirty     Size    Alloc     Free
                ------   ------   ------   ------   ------   ------   ------
  Native Heap        0        0        0        0    16998    16998    19865
  Dalvik Heap    31437    31132        0        0    49618    43555     6063
 Dalvik Other     1164     1164        0        0                           
        Stack      192      192        0        0                           
    Other dev     4348     2536        4        0                           
     .so mmap     1118      360       28        0                           
    .apk mmap      145        0        0        0                
    ...

每个应用的Native Heap占用量都不一样,但一般是10MB量级的,如果超过100MB,那很可能就是泄露了。

 

【分析步骤】

用monkey脚本复现问题:

adb shell monkey -v -v -p com.miui.home --throttle 200 --pct-touch 20 --pct-motion 25 --pct-nav 20 --pct-majornav 15 --pct-appswitch 5 --pct-anyevent 15 --pct-trackball 0 --pct-syskeys 0 --ignore-crashes --monitor-native-crashes --bugreport 5000000 logcat_file=logcat.txt >log1555.txt

在跑monkey的同时,在手机中运行shell命令,实时查看Native Heap的增长情况:

# while true then
do
date
dumpsys meminfo com.miui.home |grep "Native Heap:"
sleep 60
done

这个脚本的意思是每分钟打印一次当前时间及home应用的Native Heap大小。

跑13个小时后摘出每个小时的Native Heap大小,得出如下列表:

Tue Jan  5 16:12:53 CST 2016
         Native Heap:    13336
Tue Jan  5 17:13:00 CST 2016
         Native Heap:    17256
Tue Jan  5 18:13:35 CST 2016
         Native Heap:    21476
Tue Jan  5 19:13:09 CST 2016
         Native Heap:    25836
Tue Jan  5 20:13:56 CST 2016me
         Native Heap:    30176
Tue Jan  5 21:13:31 CST 2016
         Native Heap:    34836
Tue Jan  5 22:13:07 CST 2016
         Native Heap:    38564
Tue Jan  5 23:13:44 CST 2016
         Native Heap:    42948
Wed Jan  6 00:13:20 CST 2016
         Native Heap:    47216
Wed Jan  6 01:13:57 CST 2016
         Native Heap:    50720
Wed Jan  6 02:14:34 CST 2016
         Native Heap:    55288
Wed Jan  6 03:14:11 CST 2016
         Native Heap:    59360
Wed Jan  6 04:14:49 CST 2016
         Native Heap:    63648
Wed Jan  6 05:15:26 CST 2016
         Native Heap:    67648
Wed Jan  6 05:39:41 CST 2016
         Native Heap:    69440

从上面列表可以看出Native Heap在稳步增长,由此可以看出,确实存在泄露。

而且可以判断可能是基础模块、基础操作有泄露,很可能手动测试就能测出。

 

将shell脚本的sleep时间缩短为10秒,然后手动操作home界面,发现每次滑动屏幕作左右屏切换时,就会泄露。

进一步通过多种尝试,发现是桌面上存在文件夹图标时,每次左右滑动屏幕必现泄露,而只有应用图标时,则不会泄露。

有了必现路径以后,就可以加log调试了。

 

libc的malloc有debug功能,只需要改如下代码:

// The value of libc.debug.malloc.
#if !defined(LIBC_STATIC)
static int g_malloc_debug_level = 2;  /*0;*/
#endif

将g_malloc_debug_level从0改成2后,重新生成libc.so和libc_malloc_debug_leak.so后push到手机并重启。

重启后系统中的malloc就会是带有debug功能的malloc了,有啥区别呢?代码如下:

extern "C" void* leak_malloc(size_t bytes) {
    if (DebugCallsDisabled()) {
        return g_malloc_dispatch->malloc(bytes);
    }

    // allocate enough space infront of the allocation to store the pointer for
    // the alloc structure. This will making free‘ing the structer really fast!

    // 1. allocate enough memory and include our header
    // 2. set the base pointer to be right after our header

    size_t size = bytes + sizeof(AllocationEntry);
    if (size < bytes) { // Overflow.
        errno = ENOMEM;
        return NULL;
    }

    void* base = g_malloc_dispatch->malloc(size);
    if (base != NULL) {
        uintptr_t backtrace[BACKTRACE_SIZE];
        size_t numEntries = GET_BACKTRACE(backtrace, BACKTRACE_SIZE);

        AllocationEntry* header = reinterpret_cast<AllocationEntry*>(base);
        header->entry = record_backtrace(backtrace, numEntries, bytes);
        header->guard = GUARD;

        // now increment base to point to after our header.
        // this should just work since our header is 8 bytes.
        base = reinterpret_cast<AllocationEntry*>(base) + 1;
    }

    return base;
}

从代码中可以看出,memleak版本的malloc,再申请内存的时候,会保存当前调用栈。

也就是说每一个malloc都会对应一个调用栈,这样我们后续就可以知道泄露的内存是从哪段代码里申请的。

但这种方式有一个问题,就是保存调用栈本身很消耗内存和CPU资源。

而我们系统中malloc/free的调用是非常频繁(每分钟几十万次)的,

所以打开这个debug功能后,系统会非常卡,甚至会出现ANR等一系列系统问题。

所以这个debug功能的实用性不是很强。

 

好在我们这个问题有必现路径,所以不需要运行太长时间。

打开调试开关,链接eclipse,操作home几分钟后用ddms的native heap的dump功能,

查出有一个从libhwui.so调用的malloc占用的特别多。

但程序无法运行太长时间,加上调用栈和so对不上(不知道什么原因),所以只能怀疑是这个libhwui.so库有泄露。

 

我们关注的只是home应用,而打开libc的debug开关后,会影响系统里的所有进程,所以这种调试方式有很多不必要的资源浪费。

好在我们有INJECT工具,可以只针对特定的进程加调试code。

 

关于INJECT:

1、每个进程都会共享so库的code段,但数据段是各自有一个备份。

比如应用A和应用B都会共享libc的malloc函数,但g_malloc_debug_level变量,他们有各自的一个备份。

2、每个模块调用外部模块的函数时,会从一个叫got表的数据区取得目标函数的地址,然后跳转过去的。

got表的每个表项都存有一个外部函数的地址,是一一对应的。而进程启动过程中linker加载动态库时,为每个动态库填写他们的got表。

比如libandroid_runtime.so和libcutils.so都有各自的got表,got表中对应malloc函数的表项里存放有malloc函数的实际地址。

malloc函数的实际地址是linker加载libc.so后确定的,然后再加载libcutils.so时,将malloc函数的实际地址值填写到libc.so的got表中,malloc对应的表项里。

3、修改代码会影响系统中所有进程,但修改got表只影响单个进程。甚至只影响单个进程的单个模块。

比如我们把A进程的libcutils.so的got表中malloc函数的跳转地址改掉,那只会影响libcutils.so中调用的malloc,libandroid_runtime.so中调用的malloc则不会受影响。

4、我们可以自己定义一个inject_malloc,里面可以加一些调试code,如打印LR寄存器,打印调用栈等,然后再调用malloc。

如:

extern "C" void* inject_malloc(size_t bytes){
    GET_LINK_REGISTER(_lr);
    void* ptr;
    ptr = malloc(bytes);
    LOGD("malloc ptr=%p,size=%08lld,lr=%p",ptr,(unsigned long long)bytes,(void*)_lr);
    return ptr;
}

extern "C" void inject_free(void* p){
    if(p) {
        LOGD("free   ptr=%p",p);
    }
    free(p);
}

由于还不能确定到底是哪个so库泄露,

所以先将home应用加载的所有so库的got表中的malloc和free的跳转地址,改成自定义的inject_malloc/inject_free,

并在自定义的函数里打印如上的LOG,其中malloc函数中,除了要打印buffer地址和大小外,还会打印LR寄存器的值。

这样我们就能确定是从哪个库里调用了malloc。

...
36037:01-07 10:11:25.662  9388  9437 D INJECT  : malloc ptr=0xa7e84800,size=00008196,lr=0xb5d9260d
36038:01-07 10:11:25.663  9388  9437 D INJECT  : free   ptr=0x9934fc00
36041:01-07 10:11:25.664  9388  9388 D INJECT  : malloc ptr=0x99372c00,size=00004100,lr=0xb5d926af
36044:01-07 10:11:25.668  9388  9388 D INJECT  : malloc ptr=0x99376400,size=00004100,lr=0xb5d926af
36053:01-07 10:11:25.669  9388  9437 D INJECT  : free   ptr=0xa7e84800
36064:01-07 10:11:25.684  9388  9437 D INJECT  : free   ptr=0x99377800
36093:01-07 10:11:25.703  9388  9437 D INJECT  : free   ptr=0x9936f000
36132:01-07 10:11:25.726  9388  9437 D INJECT  : free   ptr=0x99378c00
36165:01-07 10:11:25.748  9388  9437 D INJECT  : free   ptr=0x99298800
36198:01-07 10:11:25.767  9388  9437 D INJECT  : malloc ptr=0x99298800,size=00004100,lr=0xb5d926af
36208:01-07 10:11:25.782  9388  9437 D INJECT  : malloc ptr=0xa7e84800,size=00008196,lr=0xb5d9260d
...

复现问题,得到如上的log后除去成对出现的malloc和free,如上面的红色部分。

这样剩下的只有malloc,没有free的就是泄露的buffer了。

 

虽然只有home应用打印log,但数据两还是很庞大的,得用脚本或程序解析这个log。

我这里是用程序解析,解析后的结论是,95%以上没free的malloc都有如下共同点:

1、size大小为4100,

2、LR为0xb5d926af

通过map表,可以查到0xb5d926af是libhwui.so的code段,因此确定libhwui.so中有泄露。

 

下一步INJECT只需要修改libhwui.so的malloc/free对应的跳转地址即可。

由于log量进一步减少,所以我们可以在malloc/free时多加一些调试code,比如调用栈:

extern "C" void* inject_malloc(size_t bytes){
    GET_LINK_REGISTER(_lr);
    void* ptr;
    ptr = malloc(bytes);
    LOGD("malloc ptr=%p,size=%08lld,lr=%p",ptr,(unsigned long long)bytes,(void*)_lr);
    if(bytes == 4100) {
         android::CallStack stack(LOG_TAG,3);
    }
    return ptr;
}

同样,利用解析程序可以得出泄露时候的调用栈如下:

01-07 15:32:14.108 12058 12091 D INJECT  : malloc ptr=0xa9948400,size=00004100,lr=0xb5d926af
01-07 15:32:14.122 12058 12091 D INJECT  : #00 pc 00052905  /system/lib/libhwui.so
01-07 15:32:14.122 12058 12091 D INJECT  : #01 pc 00039b2f  /system/lib/libhwui.so
01-07 15:32:14.122 12058 12091 D INJECT  : #02 pc 0003d7f1  /system/lib/libhwui.so
01-07 15:32:14.122 12058 12091 D INJECT  : #03 pc 0001a761  /system/lib/libhwui.so
01-07 15:32:14.122 12058 12091 D INJECT  : #04 pc 0001c3ef  /system/lib/libhwui.so
01-07 15:32:14.122 12058 12091 D INJECT  : #05 pc 0001f3d5  /system/lib/libhwui.so (android::uirenderer::renderthread::RenderThread::threadLoop()+80)
01-07 15:32:14.122 12058 12091 D INJECT  : #06 pc 0001006d  /system/lib/libutils.so (android::Thread::_threadLoop(void*)+112)
01-07 15:32:14.122 12058 12091 D INJECT  : #07 pc 0005fa87  /system/lib/libandroid_runtime.so (android::AndroidRuntime::javaThreadShell(void*)+70)
01-07 15:32:14.122 12058 12091 D INJECT  : #08 pc 0003f4cf  /system/lib/libc.so (__pthread_start(void*)+30)
01-07 15:32:14.122 12058 12091 D INJECT  : #09 pc 00019c2b  /system/lib/libc.so (__start_thread+6)

 

用addr2line解析:

#0层

$ addr2line -f -e libhwui.so 00052905
_ZN7android10uirenderer13DisplayListOpnwEjRNS0_15LinearAllocatorE
frameworks/base/libs/hwui/DisplayListOp.h:67

对应源码:

class DisplayListOp {
public:
    // These objects should always be allocated with a LinearAllocator, and never destroyed/deleted.
    // standard new() intentionally not implemented, and delete/deconstructor should never be used.
    virtual ~DisplayListOp() { LOG_ALWAYS_FATAL("Destructor not supported"); }
    static void operator delete(void* ptr) { LOG_ALWAYS_FATAL("delete not supported"); }
    static void* operator new(size_t size) = delete; /** PURPOSELY OMITTED **/
    static void* operator new(size_t size, LinearAllocator& allocator) {
        return allocator.alloc(size);
    }

 

#1层

$ addr2line -f -e libhwui.so 00039b2f
_ZN7android10uirenderer5Layer5deferERKNS0_14OpenGLRendererE
frameworks/base/libs/hwui/Layer.cpp:246 (discriminator 1)

对应源码:

void Layer::defer(const OpenGLRenderer& rootRenderer) {
    ATRACE_LAYER_WORK("Optimize");

    updateLightPosFromRenderer(rootRenderer);
    const float width = layer.getWidth();
    const float height = layer.getHeight();

    if (dirtyRect.isEmpty() || (dirtyRect.left <= 0 && dirtyRect.top <= 0 &&
            dirtyRect.right >= width && dirtyRect.bottom >= height)) {
        dirtyRect.set(0, 0, width, height);
    }

    deferredList.reset(new DeferredDisplayList(dirtyRect));

    DeferStateStruct deferredState(*deferredList, *renderer,
            RenderNode::kReplayFlag_ClipChildren);

    renderer->setViewport(width, height);
    renderer->setupFrameState(dirtyRect.left, dirtyRect.top,
            dirtyRect.right, dirtyRect.bottom, !isBlend());

    renderNode->computeOrdering();
    renderNode->defer(deferredState, 0);

    deferredUpdateScheduled = false;
}

 

#2层:

$ addr2line -f -e libhwui.so 0003d7f1
_ZN7android10uirenderer14OpenGLRenderer11updateLayerEPNS0_5LayerEb
frameworks/base/libs/hwui/OpenGLRenderer.cpp:389

对应源码:

bool OpenGLRenderer::updateLayer(Layer* layer, bool inFrame) {
    if (layer->deferredUpdateScheduled && layer->renderer
            && layer->renderNode.get() && layer->renderNode->isRenderable()) {

        if (inFrame) {
            endTiling();
            debugOverdraw(false, false);
        }

        if (CC_UNLIKELY(inFrame || Properties::drawDeferDisabled)) {
            layer->render(*this);
        } else {
            layer->defer(*this);
        }

        if (inFrame) {
            resumeAfterLayer();
            startTilingCurrentClip();
        }

        layer->debugDrawUpdate = Properties::debugLayersUpdates;
        layer->hasDrawnSinceUpdate = false;

        return true;
    }

    return false;
}

这里的malloc比较特殊,hwui模块自定义了内存管理模块LinearAllocator,泄露的内存就是通过这个类来管理的。

接下来就是得研究这部分代码了。

 

首先了解一下alloc过程:

我们从调用栈可以看到#1层的renderNode->defer()到#0层的allocator.alloc(size),

中间丢了很多细节。这主要是因为很多中间函数被内联展开了,所以不会在调用栈中体现。

class Layer : public VirtualLightRefBase {
public:
    ...
    sp<RenderNode> renderNode;

因此renderNode->defer()就会调用下面的函数:

void RenderNode::defer(DeferStateStruct& deferStruct, const int level) {
    DeferOperationHandler handler(deferStruct, level);
    issueOperations<DeferOperationHandler>(deferStruct.mRenderer, handler);
} 

template <class T>
void RenderNode::issueOperations(OpenGLRenderer& renderer, T& handler) {
    ...
    LinearAllocator& alloc = handler.allocator();
    int restoreTo = renderer.getSaveCount();
    handler(new (alloc) SaveOp(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag),
                   PROPERTY_SAVECOUNT, properties().getClipToBounds());
    ...

其中SaveOp是继承自DisplayListOp:

class DisplayListOp {
public:
    ...
    static void* operator new(size_t size, LinearAllocator& allocator) {
        return allocator.alloc(size);
    }

class StateOp : public DisplayListOp {
   ...
class SaveOp : public StateOp {

DispayListOp重载了new操作符,allocator.alloc()相当于

RenderNode::issueOperations()中的handler.allocator().alloc(),

而这个handler是DeferOperationHandler()类对象,它的allocator()定义如下:

class DeferOperationHandler {
public:
    DeferOperationHandler(DeferStateStruct& deferStruct, int level)
        : mDeferStruct(deferStruct), mLevel(level) {}
    ...
    inline LinearAllocator& allocator() { return *(mDeferStruct.mAllocator); }
    ...
};

allocator()是mDeferStruct.mAllocator,而mDeferStruct又是构造函数中的参数。

RenderNode::defer()中调用了DeferOperationHandler()的构造函数,并传入参数deferStruct。

而RenderNode::defer()中的deferStruct又是其上一级函数Layer::defer()中构造并传进来的。

void Layer::defer(const OpenGLRenderer& rootRenderer) {
    ...
    deferredList.reset(new DeferredDisplayList(dirtyRect));

    DeferStateStruct deferredState(*deferredList, *renderer,
            RenderNode::kReplayFlag_ClipChildren);
    ...
    renderNode->defer(deferredState, 0);

现在我们要找deferredState的mAllocator:

struct PlaybackStateStruct {
protected:
    PlaybackStateStruct(OpenGLRenderer& renderer, int replayFlags, LinearAllocator* allocator)
            : mRenderer(renderer)
            , mReplayFlags(replayFlags)
            , mAllocator(allocator) {}
public:
    ...
    LinearAllocator * const mAllocator;
    ...
};
...
struct DeferStateStruct : public PlaybackStateStruct {
    DeferStateStruct(DeferredDisplayList& deferredList, OpenGLRenderer& renderer, int replayFlags)
            : PlaybackStateStruct(renderer, replayFlags, &(deferredList.mAllocator)),
            mDeferredList(deferredList) {}
    DeferredDisplayList& mDeferredList;
};

DeferStateStruct继承自PlaybackStateStruct,

其mAllocator是构造函数中转进来的deferredList的mAllocator。

而这个deferredList就是Layer::defer()中new出来的。

deferredList.reset(new DeferredDisplayList(dirtyRect));

DeferredDisplayList()类的定义及它的mAllocator如下:

class DeferredDisplayList {
public:
    ...
    LinearAllocator mAllocator;
};

注意这里的mAllocator已经不是指针了,这意味着它的构造和析构是构造和析构DeferredDisplayList时被调用的。

自此,绕了一大圈我们找到了#0中return allocator.alloc(size);中的alloctor,它就是DeferredDisplayList中的mAllocator。

继续看LinearAllocator::alloc():

void* LinearAllocator::alloc(size_t size) {
    size = ALIGN(size);
    ...
    ensureNext(size);
    void* ptr = mNext;
    mNext = ((char*)mNext) + size;
    mWastedSpace -= size;
    return ptr;
}

void LinearAllocator::ensureNext(size_t size) {
    if (fitsInCurrentPage(size)) return;
    if (mCurrentPage && mPageSize < MAX_PAGE_SIZE) {
        mPageSize = min(MAX_PAGE_SIZE, mPageSize * 2);
        mPageSize = ALIGN(mPageSize);
    }
    mWastedSpace += mPageSize;
    Page* p = newPage(mPageSize);
    if (mCurrentPage) {
        mCurrentPage->setNext(p);
    }
    mCurrentPage = p;
    if (!mPages) {
        mPages = mCurrentPage;
    }
    mNext = start(mCurrentPage);
}

LinearAllocator::Page* LinearAllocator::newPage(size_t pageSize) {
    pageSize = ALIGN(pageSize + sizeof(LinearAllocator::Page));
    ...
    void* buf = malloc(pageSize);    // <<<<这里申请内存
    return new (buf) Page();
}

LinearAllocator::LinearAllocator()
    : mPageSize(INITIAL_PAGE_SIZE)
    ...
    , mDedicatedPageCount(0) {}

mPageSize的初始值INITIAL_PAGE_SIZE是4096,

所以newPage中加上LinearAllocator::Page的大小后,最终pageSized大小就是4100,这与前面得出的结论是一致的。

 

关于LinearAllocator:

LinearAllocator是管理hwui模块的内存管理单元,由于hwui频繁申请和释放小字节的对象,

如果每个对象都用系统的malloc,则会增加内存碎片并降低效率,所以这里就用内存池管理这些小内存。

第一次申请时先从用malloc向系统申请4KB的堆内存作为内存池(Page类),

后面申请的小内存都从这个内存池里拿(fitsInCurrentPage()就是做这事情的)。

当这个4KB的内存池满了以后,它会再次调用malloc申请8KB的内存,然后将这个作为新的内存池,加入到内存池链表中。

这样的做法有个限制,就是不方便释放。

因为如果要释放内存,我们还得用额外的机制来管理这些释放的内存,这样会增加模块复杂度,效率也会降低。

所以LinearAllocator的做法是,当LinearAllocator析构时,才将内存池链表里的内存给释放掉。如:

LinearAllocator::~LinearAllocator(void) {
    while (mDtorList) {
        auto node = mDtorList;
        mDtorList = node->next;
        node->dtor(node->addr);
    }
    Page* p = mPages;
    while (p) {
        Page* next = p->next();
        p->~Page();
        free(p);                                              // <<<<这里释放内存
        RM_ALLOCATION(mPageSize);
        p = next;
    }
}

如果这里有内存泄露,只有两种可能性

1、内存池一直在涨,LinearAllocator不会被析构

2、LinearAllocator本身有泄露,也就是只有构造,没有析构。

 

在ensureNext()里调用newPage()之前将mPageSize的大小打印出来,

发现绝大部分mPageSize的size是4096,偶尔会有一两个8192,因此可以判断LinearAllocator有泄露。

我只需要在LinearAllocator的构造和析构函数里打印log,看是否成对出现就可以了。

复测的结果确定是LinearAllocator对象有泄露,

又LinearAllocator是DeferredDisplayList的成员,可以判断DeferredDisplayList对象泄露了。

用同样的方法,很容易确定DeferredDisplayList对象有泄露。

在DeferredDisplayList的构造和析构函数里打印调用栈,

发现都是指向Layer::defer()中的deferredList.reset(new DeferredDisplayList(dirtyRect)):

void Layer::defer(const OpenGLRenderer& rootRenderer) {
    ...
    deferredList.reset(new DeferredDisplayList(dirtyRect));   //<<<<<<

    DeferStateStruct deferredState(*deferredList, *renderer,
            RenderNode::kReplayFlag_ClipChildren);
    ...
    renderNode->defer(deferredState, 0);

构造指向这一行很好理解,为什么析构函数会指向这里呢?

从deferredList的定义可以知道,deferredList是智能指针。

std::unique_ptr<DeferredDisplayList> deferredList;

它的reset()方法会将旧的指针替换为新的指针,并调用旧指针指向的对象的析构函数。如:

  template <typename _Tp, typename _Dp = default_delete<_Tp> >
    class unique_ptr
    {
      ...
      void
      reset(pointer __p = pointer()) noexcept
      {
        using std::swap;
        swap(std::get<0>(_M_t), __p);
        if (__p != pointer())
          get_deleter()(__p);
      }
      ...

从log中我们还可以看到虽然DeferredDisplayList的构造和析构的调用栈都指向同一个地址,但它们并非成对出现。

构造会比析构多很多,也就是说当reset()时deferredList如果指向空指针,这里就不会有析构函数了。

既然不成对出现,那肯定还有别的地方要析构这个DeferredDisplayList对象。而要析构这个对象肯定会用到deferredList。

由于deferredList是private的成员,所以肯定只会在DeferredDisplayList.cpp和DeferredDisplayList.h中会用到。

可疑的代码只有一处:

void Layer::cancelDefer() {
    renderNode = nullptr;
    deferredUpdateScheduled = false;
    deferredList.release();
}

在这里将deferredList指向的指针打印出来,发现只要在这里打出来的地址必定是泄露的。

且deferredList.release()时不会调用DeferredDisplayList()的析构函数。

Layer::defer()和Layer::cancelDefer()代码上是挨着写的,

既然Layer::defer()里会申请内存,那Layer::cancelDefer()里应该会释放内存。

难道作者对智能指针的理解有误?首先从代码角度确定release()确实不会调用析构函数。

      pointer
      release() noexcept
      {
        pointer __p = get();
        std::get<0>(_M_t) = pointer();
        return __p;
      }

显然,这里确实不存在析构。

为此在cancelDefer()里加上deferredList.reset(0);

void Layer::cancelDefer() {
    renderNode = nullptr;
    deferredUpdateScheduled = false;
    deferredList.reset(0);
    deferredList.release();
}

重新编译libhwui.so,push后重启手机,反复滑动桌面,泄露现象不再了。

这样基本上就能确定问题了。应该是原生问题。

 

从git log中发现这段code是14年底时候在M上加上去的。L的deferredList是普通指针。修改记录如下:

diff --git a/libs/hwui/Layer.h b/libs/hwui/Layer.h
-    DeferredDisplayList* deferredList;
+    std::unique_ptr<DeferredDisplayList> deferredList;

diff --git a/libs/hwui/Layer.cpp b/libs/hwui/Layer.cpp
void Layer::cancelDefer() {
     renderNode = NULL;
     deferredUpdateScheduled = false;
-    if (deferredList) {
-        delete deferredList;
-        deferredList = NULL;
-    }
+    deferredList.release();
}

用L试了下确实没泄露,只有M的机型包括NEXUS也有泄露。

接着给根据提交记录找到google工程师的邮箱,发邮件反馈的一下。

没过10分钟就收到回复说这个问题已经解决,并提供了path。

 

【解决方案】

diff --git a/libs/hwui/Layer.cpp b/libs/hwui/Layer.cpp
void Layer::cancelDefer() {
     renderNode = NULL;
     deferredUpdateScheduled = false;
-    deferredList.release();
+   deferredList.reset(nullptr);
}

该修改是1个月前提交的,还是比google工程师晚了一步!

 

以上是关于Layer的内存泄露问题分析报告的主要内容,如果未能解决你的问题,请参考以下文章

5.JVM系列-堆内内存泄露案例分析解决

为啥这段代码会泄露? (简单的代码片段)

内存泄露分析

ThreadLocal内存泄露原因分析

干货:一次内存泄露的实战分析过程

Linux下利用Valgrind工具进行内存泄露检测和性能分析