android 如何分析应用的内存(六)

这篇具有很好参考价值的文章主要介绍了android 如何分析应用的内存(六)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

android 如何分析应用的内存(六)

接上文,本系列文章,最重要的部分——————对native堆内存的分析,即将上演

分成六大板块:

  1. 手动实现,new和delete,以及malloc和freee,并统计内存分配情况
  2. 使用malloc hook
  3. 使用Malloc debug和libc回调
  4. 使用malloc统计和libmemunreachable
  5. 使用HWASan/Asan工具,查找内存错误
  6. 使用perfetto工具,他也可以分析java部分

本篇文章,实现第一个板块,并作为后续内存分析的基础知识。

先对,用到的理论知识,做说明,然后再进行实现。

理论篇

我们先简单介绍操作系统的系统调用,然后简单说明android libc对其的封装,最后是我们自己对libc的分配函数的封装,以达到内存统计分析的目的

系统调用

对于内存而言,它属于系统资源,它的管理者是操作系统。而为了让用户能够使用系统的资源。有三种系统调用,提供给用户。分别是:

  1. brk系统调用:用于扩展和收缩进程的数据段
  2. sbrk系统调用:同brk
  3. mmap/munmap系统调用:用于在进程的地址空间,创建/取消内存映射
    除了上面提供的以外,还有一些更高级的系统调用,此处不表

其中brk系统调用和sbrk系统调用的区别是:brk用于直接设置break地址。即修改进程数据段的结束地址。
sbrk用于在原有的break地址上,进行增加或减少

mmap和munmap则是,直接进行进程地址空间的映射。

libc库

从上面可以知道,操作系统只提供了一些非常简单的内存操作接口,
如果一个进程中,有多个线程,应该怎么管理这部分堆空间呢?
如果一个线程要反反复复频繁的分配空间怎么办呢?是否要频繁的调用系统调用呢?
如果分配的空间,一会大的要死,一会儿小的要死,应该怎么办呢?

此时,Android的libc库,对上面的内存做了进一步的管理。它使用了一种被称为jemalloc的分配器策略。

jemalloc,维护了大小不同的内存池。当应用程序请求分配内存时,jemalloc会选择合适的内存满足应用的请求。
jemalloc,还为每个线程维护本地缓存,当线程请求内存时,则尽可能返回本地缓存中的内存块。

jemalloc的所有这些内存管理,都是基于上面介绍的三种系统调用。

同时Android的libc库,也是一种轻量级的c库,因此对jemalloc的分配器策略,进行了一种标准接口的封装
即我们熟悉的:malloc,remalloc,calloc标准接口

当应用程序,调用malloc,remalloc,或者calloc函数请求分配内存时.libc库则根据jemalloc的分配器策略分配不同的内存快

自定义内存分配函数

那么我们还可以对,libc库的内存分配函数,做进一步的封装。在应用每次调用内存分配的时候,记录下调用分配的堆栈,和分配的大小。
在合适的时候,将其打印出来,以观察内存的分配情况,从而达到分析内存泄漏的问题。

malloc函数,free函数

malloc,free函数位于stdlib.h文件中,定义如下

void* malloc (size_t size);
void free (void* ptr);

下面是我自定义的malloc函数,我将其放在一个单独的命名为Memory.cpp的文件中

void* malloc(size_t size) {
    void* ptr = NULL;

    // 在这里,你可以添加自定义的逻辑,例如记录内存分配的信息,或者改变内存分配的行为。

    // 调用系统的malloc函数进行实际的内存分配。
    // 注意,这里需要使用函数指针来调用系统的malloc函数,以避免无限递归。
    void* (*libc_malloc)(size_t) = (void* (*)(size_t))dlsym(RTLD_NEXT, "malloc");
    //查找下一个叫做malloc的符号
    if (libc_malloc) {
        ptr = libc_malloc(size);
        
        //将ptr保存,以便于后续分析
        addToAllptr(ptr,size);
    }

    return ptr;
}

为了保存,分配的地址和,大小,新增下面的函数

//保存所有指针的地方
AllPtr ptrBuffer;

static std::recursive_mutex mutex;

//为了在ptrBuffer.push的时候,防止循环调用
static bool * isPush = nullptr;

void addToAllptr(void *ptr,std::size_t sz){
    std::unique_lock<std::recursive_mutex> _l(mutex);
    //为什么不直接用一个bool变量?
    //因为发现,在部分版本中,会被优化掉,因此通过使用单独的内存来存储这个bool值
    if(!isPush){
        void* (*libc_malloc)(size_t) = (void* (*)(size_t))dlsym(RTLD_NEXT, "malloc");
        if (libc_malloc) {
            isPush =(bool *) libc_malloc(sizeof(bool));
            *isPush = false;
        }
    }
    if(isPush){
        if(*isPush){

        }else{
            *isPush = true;
            ptrBuffer.push(ptr,sz);
            *isPush = false;
        }
    }
}

void popFromAllptr(void *ptr) {
    std::unique_lock<std::recursive_mutex> _l(mutex);
    ptrBuffer.pop(ptr);
}

其中。prtBuffer是类型为Allptr的一个自定义类,这个类负责管理,分配的内存。代码如下:

struct AllPtr{

    //每次内存的分配,就会有一个PtrItem与之对应
    struct PtrItem{
        bool isEmpty = true;//表示该PtrItem是否为空
        void * ptr = nullptr; //存储分配的指针
        std::size_t size = 0;//存储分配的内存大小
        struct timespec now;//存储分配内存的时间
        char * stack = nullptr;//存储分配内存的调用栈
    };

    PtrItem * allptr = nullptr;//PtrItem数组头指针
    std::size_t size = 0;//PtrItem数组大小
    int line = 20;//辅助打印的行数

    //判断PtrItem数组是否满
    bool isFull() const{
        bool full = true;
        for(auto i =0;i<size;i++){
            if(allptr[i].isEmpty){
                full = false;
                break;
            }
        }
        return full;
    }

    //创建一个新的PtrItem数组,它的大小是老数组大小的2倍,并将老数组内容,复制到新数组中
    void createAndCopy(){
        std::size_t newSize = 0;
        if(size > 0 ){
            newSize = size * 2;
            ALOG(LOG_INFO, __FUNCTION__ , "create and copy %lu",newSize);
        }else{
            newSize = 1;
        }
        void* (*libc_malloc)(size_t) = (void* (*)(size_t))dlsym(RTLD_NEXT, "malloc");
        if (!libc_malloc) {
            ALOG(LOG_ERROR, __FUNCTION__ , "find lib malloc error");
            return ;
        }
        auto newPtr = (PtrItem *)libc_malloc(sizeof(PtrItem) * newSize);
        for(int i= 0;i<newSize;i++){
            newPtr[i].isEmpty = true;
            newPtr[i].ptr = nullptr;
            newPtr[i].size = 0;
            newPtr[i].now.tv_sec = 0;
            newPtr[i].now.tv_nsec = 0;
            newPtr[i].stack = nullptr;
        }
        //复制老数组内容
        for(int i = 0; i < size;i++){
            newPtr[i].isEmpty = allptr[i].isEmpty;
            newPtr[i].ptr = allptr[i].ptr;
            newPtr[i].size = allptr[i].size;
            newPtr[i].now.tv_sec= allptr[i].now.tv_sec;
            newPtr[i].now.tv_nsec= allptr[i].now.tv_nsec;
            newPtr[i].stack = allptr[i].stack;
        }
        if(allptr){
            void (*lib_free)(void*) = (void  (*)(void*))dlsym(RTLD_NEXT, "free");
            if (lib_free) {
                lib_free(allptr);
            }
        }
        allptr = newPtr;
        size = newSize;
    }

    //保存ptr,新建一个PtrItem
    void push(void * ptr,std::size_t size) {
        //是否为空//是否满
        if (!allptr || isFull()) {
            createAndCopy();
        }
        for (auto i = 0; i < size; i++) {
            if (allptr[i].isEmpty) {
                allptr[i].isEmpty = false;
                allptr[i].ptr = ptr;
                allptr[i].size = size;
                if (clock_gettime(CLOCK_REALTIME, &(allptr[i].now)) == -1) {
                    perror("clock_gettime");
                }
                //initialize
                void* (*libc_malloc)(size_t) = (void* (*)(size_t))dlsym(RTLD_NEXT, "malloc");
                if (libc_malloc) {
                    allptr[i].stack = (char * )libc_malloc( 1024*line);
                    //获取对应的调用栈
                    Find::Debug().printStackTrace(allptr[i].stack,1024*line);
                }
                return;
            }
        }
    }

    //弹出Ptr
    void pop(void *ptr) const {
        for(auto i=0;i<size;i++) {
            if(allptr[i].ptr == ptr) {
                allptr[i].isEmpty = true;
                if (allptr[i].stack){
                    void (*lib_free)(void*) = (void  (*)(void*))dlsym(RTLD_NEXT, "free");
                    if (lib_free) {
                        lib_free(allptr[i].stack);
                    }
                }
                return;
            }
        }
    }

    ~AllPtr(){

        if(allptr){
            for(auto i =0;i<size;i++){
                if(!allptr[i].isEmpty){
                    if (allptr[i].stack){
                        void (*lib_free)(void*) = (void  (*)(void*))dlsym(RTLD_NEXT, "free");
                        if (lib_free) {
                            lib_free(allptr[i].stack);
                        }
                    }
                    break;
                }
            }
            void (*lib_free)(void*) = (void  (*)(void*))dlsym(RTLD_NEXT, "free");
            if (lib_free) {
                lib_free(allptr);
            }
        }
    }

    //在合适的时候,dump出PtrItem中的内容,便于分析
    //在这里,只是简单的将其输出到Log系统
    //事实上,应该按照时间进行采样,然后输出到文件中,通过其他工具将其图形化
    //这里为了简化远离,仅仅将其输出到log系统中
    void dump(){
        for(auto i=0;i<size;i++){
            if(allptr[i].isEmpty == false){
                time_t t = allptr[i].now.tv_sec;
                struct tm *local;
                local = localtime(&t);
                if (local == NULL) {
                    perror("localtime");
                }
                ALOG(LOG_INFO, __FUNCTION__ , "%02d:%02d:%02d:%04d,ptr %p,size %lu",
                    local->tm_hour,local->tm_min,local->tm_sec,allptr[i].now.tv_nsec/1000000,
                     allptr[i].ptr,allptr[i].size);
                for(int i=0;i<line;i++){
                    ALOG(LOG_INFO, __FUNCTION__ ,"%s",allptr[i].stack+(i*1024));
                }


            }
        }
    }
};

在上面的例子中,使用Find::Debug().printStackTrace来获取android的native调用栈
它的实现如下:

namespace Find {
  
    //将调用栈信息打印到buffer中
    void Debug::printStackTrace(char * buffer,int size){
        const auto maxStackDeep = 10;
        intptr_t stackBuf[maxStackDeep];
        for(int i = 0; i < maxStackDeep; ++i){
            stackBuf[i] = 0;
        }
        memset(buffer, 0, size);
        dumpBacktraceIndex(buffer, stackBuf, captureBacktrace(stackBuf, maxStackDeep));
    }

    //捕获调用栈信息
    size_t Debug::captureBacktrace(intptr_t *buffer, size_t maxStackDeep) {
        BacktraceState state = {buffer, buffer + maxStackDeep};
        _Unwind_Backtrace(unwindCallback, &state);
        return state.current - buffer;
    }

    //解析调用栈信息
    void Debug::dumpBacktraceIndex(char *out, intptr_t *buffer, size_t count) {
        for (size_t idx = 0; idx < count; ++idx) {
            intptr_t addr = buffer[idx];
            const char *symbol = "      ";
            const char *dlfile = "      ";
            void * baseA = nullptr;

            Dl_info info;
            info.dli_fbase = nullptr;
            if (dladdr((void *) addr, &info)) {
                if (info.dli_sname) {
                    symbol = info.dli_sname;
                }
                if (info.dli_fname) {
                    dlfile = info.dli_fname;
                }
                if(info.dli_fbase){
                    baseA = info.dli_fbase;
                }

            } else {
                strcat(out, "#                               \n");
                continue;
            }
            char temp[50];
            memset(temp, 0, sizeof(temp));
            sprintf(temp, "%zu", idx);
            strcat(out, "#");
            strcat(out, temp);
            strcat(out, ": ");
            memset(temp, 0, sizeof(temp));
            sprintf(temp, "%p", (void *) addr);
            strcat(out, temp);
            strcat(out, "  ");
            memset(temp, 0, sizeof(temp));
            //尤其要注意,这里的两地址相减,这是为了让addr2line工具能够正常解析
            sprintf(temp, "%p", (void *) ((long)addr -  (long )baseA));
            strcat(out, temp);
            strcat(out, "  ");
            strcat(out, symbol);
            strcat(out, "      ");
            strcat(out, dlfile);
            strcat(out, "\n");
        }
    }

    _Unwind_Reason_Code Debug::unwindCallback(struct _Unwind_Context *context, void *arg) {
        BacktraceState *state = static_cast<BacktraceState *>(arg);
        intptr_t ip = (intptr_t) _Unwind_GetIP(context);
        if (ip) {
            if (state->current == state->end) {
                return _URC_END_OF_STACK;
            } else {
                state->current[0] = ip;
                state->current++;
            }
        }
        return _URC_NO_REASON;


    }
}

注意:在Debug中,并没有使用backtrace函数,这是因为,backtrace函数是由GNUC库提供的。而android的c库,是一个被称为bionic的Google单独编写的c库,并没有提供backtrace函数。因此我们选择了libunwind库中的_Unwind_Backtrace。

然后使用dladdr函数进行地址的解析。

需要再次注意的是:addr地址为内存中的地址,并不能直接使用
addr2line进行直接转换。需要将addr和共享库的基址相减,得到相对地址,才能够被add2line进行转化。

有了上面的malloc,还需要有free函数,如下

//释放对应的ptr
void free (void* ptr){
    //删除Allptr中的ptr
    popFromAllptr(ptr);
    void (*lib_free)(void*) = (void  (*)(void*))dlsym(RTLD_NEXT, "free");
    if (lib_free) {
        lib_free(ptr);
    }
}

增加一个广播,用于测试

为了能够直观的观测到记录下来的内存,现在增加一个广播,如下:

application.registerReceiver(object : BroadcastReceiver() {
            override fun onReceive(context: Context, intent: Intent) {
                Timber.d(intent.action)
                native_dumpCpluplusObjecs()
            }
        }, IntentFilter("find.dump.objects"))

当收到"find.dump.objects"时,会调用native_dumpCpluplusObjecs。它的实现如下:


extern AllPtr ptrBuffer;

extern "C"
JNIEXPORT void JNICALL
Java_cn_findpiano_romsdk_hardware_midi_MidiManager_native_1dumpCpluplusObjecs(JNIEnv *env,
                                                                              jobject thiz) {
    ptrBuffer.dump();
}

我们调用ptrBuffer的dump函数,将记录下来的内存信息输出到log系统中。

现在进入adb shell ,输入如下的命令

am broadcast -a find.dump.objects

那么将会在log系统看到如下的输出
android 如何分析应用的内存(六)

注意:自定义malloc函数,并不会影响realloc和calloc,因为我目前的项目并没有使用这两函数,因此也没有做演示,但他们的核心思想同malloc一样

c++中的new和delete实现

对于c++这门语言而言,它的new和delete以及new[] delete[]都是直接调用对应的运算符函数,如void* operator new(std::size_t sz);

因此我们可以重载这些运算符函数,以达到监控内存的目的。事实上android的c++库,不管是来自LLVM项目的libc++库。还是,来自android自定义的libstdc++库,都会间接调用,android的libc库的分配函数和释放函数。

注意注意:Android的libstdc++.so和来自GNU项目的libstdc++.so是不一样的。具体原因可以参看
ndk的问题744:https://github.com/android/ndk/issues/744

即然会间接调用libc的分配函数,那么上面关于malloc和free的实现,也已经覆盖了c++的使用。

但是这里为了演示说明,将重新实现void* operator new(std::size_t sz),然后它使用系统提供的malloc,而不是我们自己实现的malloc,下面的代码来自c++中的例子,并增加了部分,前面介绍的内容,如使用libc中的malloc

void* operator new(std::size_t sz)
{
    if (sz == 0)
        ++sz; // avoid std::malloc(0) which may return nullptr on success
    void* (*libc_malloc)(size_t) = (void* (*)(size_t))dlsym(RTLD_NEXT, "malloc");
    if (libc_malloc) {
        if (void *ptr = libc_malloc(sz)){
            ALOGD("new an object %lu,ptr %p",sz,ptr);
            addToAllptr(ptr,sz);
            return ptr;
        }
    }

    throw std::bad_alloc{}; 
}

// no inline, required by [replacement.functions]/3
void* operator new[](std::size_t sz)
{
    if (sz == 0)
        ++sz; // avoid std::malloc(0) which may return nullptr on success

    void* (*libc_malloc)(size_t) = (void* (*)(size_t))dlsym(RTLD_NEXT, "malloc");
    if (libc_malloc) {
        if (void *ptr = libc_malloc(sz)){
            ALOGD("new an array object %lu,ptr %p",sz,ptr);
            addToAllptr(ptr,sz);
            return ptr;
        }
    }
    throw std::bad_alloc{}; 
}

void operator delete(void* ptr) noexcept
{
    ALOGD("delete an object %p",ptr);
    popFromAllptr(ptr);
    void (*lib_free)(void*) = (void  (*)(void*))dlsym(RTLD_NEXT, "free");
    if (lib_free) {
        lib_free(ptr);
    }
}


void operator delete[](void* ptr) noexcept
{
    ALOGD("delete an array object %p",ptr);
    popFromAllptr(ptr);
    void (*lib_free)(void*) = (void  (*)(void*))dlsym(RTLD_NEXT, "free");
    if (lib_free) {
        lib_free(ptr);
    }
}

上面关于c++的new和delete仅仅是演示使用。c++提供了更加丰富的自定义分配函数的操作。

注意:在上面的代码中,我们使用了dlsym(RTLD_NEXT, “malloc”);来查找下一个叫做malloc的函数,如果有多个so库且都含有malloc,这样的行为,将是根据搜索路径中出现的先后顺序决定。

通过在一段时间中,多次dump,然后使用shell脚本,按照时间排序,可以非常轻松的观测到长期占据且没有释放的内存。然后使用对应的调用栈,找到源码进行分析。

至此,关于手动实现内存的分配和释放,并保存调用栈。已完成。

使用此种方法,基本上只会影响本so库中内容,这取决于编译链接的机制,那么怎样能够监控所有的内存分配呢,下一节molloc_hooks将会介绍这部分内容。敬请期待文章来源地址https://www.toymoban.com/news/detail-481094.html

到了这里,关于android 如何分析应用的内存(六)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • android 如何分析应用的内存(十五)——Visual Studio Code 调试Android应用

    在上一篇文章介绍了jdb调试java应用 接下来介绍用UI界面调试java应用,达到同jdb一样的效果。 同样的UI界面有很多选择,如Eclipse,Android Studio,Visual Studio Code.因为Android Studio的诸多不便,结合自身的使用习惯,这里推荐并介绍Visual Studio Code 安装必要的插件 在插件市场中,搜索

    2024年02月14日
    浏览(51)
  • android 如何分析应用的内存(十七)——使用MAT查看Android堆

    前一篇文章,介绍了使用Android profiler中的memory profiler来查看Android的堆情况。 如Android 堆中有哪些对象,这些对象的引用情况是什么样子的。 可是我们依然面临一个比较严峻的挑战:不管是app开发者,还是内存分析者而言,堆中的对象,非常之多,不仅有Android 原生的类,还

    2024年02月13日
    浏览(69)
  • android 如何分析应用的内存(八)——Android 7.0以后的malloc debug

    接上文,介绍六大板块中的第三个————malloc调试和libc回调 上一篇文章中,仅仅是在分配和释放的时候,拦截对应的操作。而不能进一步的去检查内存问题。比如:释放之后再次使用指针,内存泄漏,内存损坏等等。 在这篇文章中,将会介绍malloc调试技术,它可以对nat

    2024年02月10日
    浏览(43)
  • 如何使用KoodousFinder搜索和分析Android应用程序中的安全威胁

    KoodousFinder是一款功能强大的Android应用程序安全工具,在该工具的帮助下,广大研究人员可以轻松对目标Android应用程序执行安全研究和分析任务,并寻找出目标应用程序中潜在的安全威胁和安全漏洞。 在使用该工具之前,我们首选需要访问该工具的【开发者门户】创建一个

    2024年02月13日
    浏览(61)
  • Android 内存分析(java/native heap内存、虚拟内存、处理器内存 )

    1.jvm 堆内存(dalvik 堆内存) 不同手机中app进程的 jvm 堆内存是不同的,因厂商在出厂设备时会自定义设置其峰值。比如,在Android Studio 创建模拟器时,会设置 jvm heap 默认384m , 如下图所示: 当app 进程中java 层 new 对象(加起来总和)占用的堆内存达到jvm heap 峰值时,就会抛出OOM 。

    2024年02月14日
    浏览(47)
  • Android Profiler 内存分析器使用

    Android Profiler是Android Studio的一部分,提供了一个集成的性能分析工具套件,包括内存分析。Android Profiler 工具可提供实时数据,帮助您了解应用的 CPU、内存、网络和电池资源使用情况。 在Android Profiler中,您可以查看内存使用情况的实时图表、堆转储快照、分析内存泄漏等,

    2024年02月08日
    浏览(49)
  • Android内存泄漏分析及检测工具LeakCanary简介,Android进阶

    @Synchronized override fun expectWeaklyReachable( watchedObject: Any, description: String ) { if (!isEnabled()) { return } removeWeaklyReachableObjects() val key = UUID.randomUUID() .toString() val watchUptimeMillis = clock.uptimeMillis() val reference = KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue) SharkLog.d { \\\"Watching \\\" +

    2024年04月25日
    浏览(39)
  • Android 内存泄漏、性能分析常用工具

    一、内存泄漏 1、 MAT-eclipse :“Memory Analyzer Tool”,一个基于Eclipse的内存分析工具,是一个快速、功能丰富的JAVA heap分析工具,它可以帮助我们查找内存泄漏和减少内存消耗。 2、Leakcanary :一款开源的自动检测内存泄漏的工具。 3、AndroidStudio Profiler :Android Studio 3.0 采用全新

    2024年02月12日
    浏览(48)
  • 【Android】一个contentResolver引起的内存泄漏问题分析

    长时间的压力测试后,系统发生了重启,报错log如下 JNI ERROR (app bug): global reference table overflow (max=51200) global reference table overflow的log 08-08 04:11:53.052912   973  3243 F zygote64: indirect_reference_table.cc:256] JNI ERROR (app bug): global reference table overflow (max=51200) 08-08 04:11:53.053014   973  3243 F z

    2024年02月08日
    浏览(36)
  • android studio内存分析之Memory profiler的使用

    Android Studio中内存分析工具Memory profiler的使用 参考文章 1. 打开Memory Profiler 有两种方式打开,第一种通过标题栏打开: 第二种通过下方菜单栏打开 2. 工具使用 打开后是这样的: 打开后,点击 + 号,选择自己包名 选择完成后,就会创建你项目的SESSIONS界面: 如果想删除这个

    2024年02月05日
    浏览(51)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包