• 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工程师晚了一步!

  • 相关阅读:
    es6学习之async和await关键字
    杂七杂八 2022年我今后的学习方向
    常见问题总结 某糠vms密码恢复
    软件开发 小程序分类
    Linux 命令 nproc
    java类初始化、实例初始化、方法重写、重载
    java方法的参数传递机制
    java的序列化与反序列化原理
    ThreadPoolExecutor线程池
    固定数据条的最大值
  • 原文地址:https://www.cnblogs.com/YYPapa/p/6850449.html
Copyright © 2020-2023  润新知