• debug:am dumpheap命令源码分析


    debug:am dumpheap命令源码分析

    一、源码分析

    代码基于android11。am命令的实现见debug:am、cmd命令。书接上文,

    system_server进程

    ActivityManagerShellCommand#onCommand

    frameworks/base/services/core/java/com/android/server/am/ActivityManagerShellCommand.java

     176     @Override
     177     public int onCommand(String cmd) {      
     183             switch (cmd) {
     184                 case "start":
     185                 case "start-activity":
     186                     return runStartActivity(pw);
    ......
     207                 case "dumpheap":
     208                     return runDumpHeap(pw);
    

    走到207行

    ActivityManagerShellCommand.java#runDumpHeap

    frameworks/base/services/core/java/com/android/server/am/ActivityManagerShellCommand.java

     911     int runDumpHeap(PrintWriter pw) throws RemoteException {
     912         final PrintWriter err = getErrPrintWriter();
     913         boolean managed = true;
     914         boolean mallocInfo = false;
     915         int userId = UserHandle.USER_CURRENT;
     916         boolean runGc = false;
     917 
     918         String opt;
     919         while ((opt=getNextOption()) != null) {
     920             if (opt.equals("--user")) {
     921                 userId = UserHandle.parseUserArg(getNextArgRequired());
     922                 if (userId == UserHandle.USER_ALL) {
     923                     err.println("Error: Can't dump heap with user 'all'");
     924                     return -1;
     925                 }
     926             } else if (opt.equals("-n")) {
     927                 managed = false;
     928             } else if (opt.equals("-g")) {
     929                 runGc = true;
     930             } else if (opt.equals("-m")) {
     931                 managed = false;
     932                 mallocInfo = true;
     933             } else {
     934                 err.println("Error: Unknown option: " + opt);
     935                 return -1;
     936             }
     937         }
     938         String process = getNextArgRequired();
     939         String heapFile = getNextArg();
     940         if (heapFile == null) {
     941             LocalDateTime localDateTime = LocalDateTime.now(Clock.systemDefaultZone());
     942             String logNameTimeString = LOG_NAME_TIME_FORMATTER.format(localDateTime);
     943             heapFile = "/data/local/tmp/heapdump-" + logNameTimeString + ".prof";
     944         }
     945         pw.println("File: " + heapFile);
     946         pw.flush();
     947 
     948         File file = new File(heapFile);
     949         file.delete();
     950         ParcelFileDescriptor fd = openFileForSystem(heapFile, "w");
     951         if (fd == null) {
     952             return -1;
     953         }
     955         final CountDownLatch latch = new CountDownLatch(1);
     956 
     957         final RemoteCallback finishCallback = new RemoteCallback(new OnResultListener() {
     958             @Override
     959             public void onResult(Bundle result) {
     960                 latch.countDown();
     961             }
     962         }, null);
     963 
     964         if (!mInterface.dumpHeap(process, userId, managed, mallocInfo, runGc, heapFile, fd,
     965                 finishCallback)) {
     966             err.println("HEAP DUMP FAILED on process " + process);
     967             return -1;
     968         }
     969         pw.println("Waiting for dump to finish...");
     970         pw.flush();
     971         try {
     972             latch.await();
     973         } catch (InterruptedException e) {
     974             err.println("Caught InterruptedException");
     975         }
     976 
     977         return 0;
     978     }
    

    919-943行入参处理

    • --user:用户id,默认UserHandle.USER_CURRENT
    • -n:抓natice的heapdump
    • -g:抓之前先gc一次
    • -m:隐藏参数

    另外文件名不指定的话,默认路径与文件名是/data/local/tmp/heapdump-时间辍.prof

    955-962行,使用java的CountDownLatch工具类来监听处理中止。我们也可以ctrl+c结束。

    964行,走到ams

    frameworks/base/services/core/java/com/android/server/am/ActivityManagerShellCommand.java

    18562     public boolean dumpHeap(String process, int userId, boolean managed, boolean mallocInfo,
    18563             boolean runGc, String path, ParcelFileDescriptor fd, RemoteCallback finishCallback) {
    18564 
    18565         try {
    18566             synchronized (this) {
    18567                 // note: hijacking SET_ACTIVITY_WATCHER, but should be changed to
    18568                 // its own permission (same as profileControl).
    18569                 if (checkCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER)
    18570                         != PackageManager.PERMISSION_GRANTED) {
    18571                     throw new SecurityException("Requires permission "
    18572                             + android.Manifest.permission.SET_ACTIVITY_WATCHER);
    18573                 }
    18579                 ProcessRecord proc = findProcessLocked(process, userId, "dumpHeap");
    18584                 boolean isDebuggable = "1".equals(SystemProperties.get(SYSTEM_DEBUGGABLE, "0"));
    18585                 if (!isDebuggable) {
    18586                     if ((proc.info.flags&ApplicationInfo.FLAG_DEBUGGABLE) == 0) {
    18587                         throw new SecurityException("Process not debuggable: " + proc);
    18588                     }
    18589                 }
    18590 
    18591                 mOomAdjuster.mCachedAppOptimizer.enableFreezer(false);
    18592 
    18593                 final RemoteCallback intermediateCallback = new RemoteCallback(
    18594                         new RemoteCallback.OnResultListener() {
    18595                         @Override
    18596                         public void onResult(Bundle result) {
    18597                             finishCallback.sendResult(result);
    18598                             mOomAdjuster.mCachedAppOptimizer.enableFreezer(true);
    18599                         }
    18600                     }, null);
    18601 
    18602                 proc.thread.dumpHeap(managed, mallocInfo, runGc, path, fd, intermediateCallback);
    18603                 fd = null;
    18604                 return true;
    18605             }
    18606         } catch (RemoteException e) {
    

    18569行鉴权,android.Manifest.permission.SET_ACTIVITY_WATCHER

    18579行,根据传进来的字符串查找对应的ProcessRecod,传pid或者进程名(包名)都行。

    18584行,设备或者app需要是debug的

    18591-18600行,类似上面的CountDownLatch。当开始抓时,不允许Freez

    18602行,和之前的trace-ipcprofile一样,这里也是bidner调用到java进程里,现在转到binder对端跟踪

    java进程

    Java dump

    ActivityThread.java$ApplicationThread#dumpHeap

    frameworks/base/core/java/android/app/ActivityThread.java

     947     private class ApplicationThread extends IApplicationThread.Stub {   
    1174         @Override
    1175         public void dumpHeap(boolean managed, boolean mallocInfo, boolean runGc, String path,
    1176                 ParcelFileDescriptor fd, RemoteCallback finishCallback) {
    1177             DumpHeapData dhd = new DumpHeapData();
    1178             dhd.managed = managed;
    1179             dhd.mallocInfo = mallocInfo;
    1180             dhd.runGc = runGc;
    1181             dhd.path = path;
    1182             try {
    1183                 // Since we're going to dump the heap asynchronously, dup the file descriptor before
    1184                 // it's closed on returning from the IPC call.
    1185                 dhd.fd = fd.dup();
    1186             } catch (IOException e) {
    1187                 Slog.e(TAG, "Failed to duplicate heap dump file descriptor", e);
    1188                 return;
    1189             } finally {
    1190                 IoUtils.closeQuietly(fd);
    1191             }
    1192             dhd.finishCallback = finishCallback;
    1193             sendMessage(H.DUMP_HEAP, dhd, 0, 0, true /*async*/);
    1194         }                 
    

    1177-1181行,用新的数据结构DumpHeapData装参数

    ActivityThread.java#handleDumpHeap

    frameworks/base/core/java/android/app/ActivityThread.java

    2006                 case DUMP_HEAP:                                                     
    2007                     handleDumpHeap((DumpHeapData) msg.obj);
    2008                     break;
    ---------------------------------------------------------------------------
    6083     static void handleDumpHeap(DumpHeapData dhd) {
    6084         if (dhd.runGc) {
    6085             System.gc();
    6086             System.runFinalization();
    6087             System.gc();
    6088         }
    6089         try (ParcelFileDescriptor fd = dhd.fd) {
    6090             if (dhd.managed) {
    6091                 Debug.dumpHprofData(dhd.path, fd.getFileDescriptor());
    6092             } else if (dhd.mallocInfo) {
    6093                 Debug.dumpNativeMallocInfo(fd.getFileDescriptor());
    6094             } else {
    6095                 Debug.dumpNativeHeap(fd.getFileDescriptor());
    6096             }
    6097         } catch (IOException e) {
    ...
    6108         try {
    6109             ActivityManager.getService().dumpHeapFinished(dhd.path);
    

    6084-6088行,命令行参数有-g的话就在这gc

    6089-6095行开始dump,由managed参数来决定抓哪个,三选一。

    结合ActivityManagerShellCommand.java#runDumpHeap方法中的参数处理,我们可以得出如下逻辑

    managed mallocInfo
    不指定-m与-n true false Debug.dumpHprofData
    指定-n false false Debug.dumpNativeHeap
    指定-m false true Debug.dumpNativeMallocInfo
    同时指定-m与-n false true Debug.dumpNativeMallocInfo

    可以看到,-m参数覆盖了-n

    6109-6114行,抓完了的回调通知。

    Debug.java#dumpHprofData

    frameworks/base/core/java/android/os/Debug.java

    2019     /**     
    2020      * Like dumpHprofData(String), but takes an already-opened
    2021      * FileDescriptor to which the trace is written.  The file name is also
    2022      * supplied simply for logging.  Makes a dup of the file descriptor.
    2023      *      
    2024      * Primarily for use by the "am" shell command.
    2025      *      
    2026      * @hide    
    2027      */     
    2028     public static void dumpHprofData(String fileName, FileDescriptor fd)               
    2029             throws IOException {
    2030         VMDebug.dumpHprofData(fileName, fd);
    2031     }       
    

    可以看到,java的heapdump是操作了虚拟机

    Native dump

    Debug.java#dumpNativeHeap/dumpNativeMallocInfo

    frameworks/base/core/java/android/os/Debug.java

    2044     /** 
    2045      * Writes native heap data to the specified file descriptor.
    2046      *  
    2047      * @hide
    2048      */
    2049     @UnsupportedAppUsage
    2050     public static native void dumpNativeHeap(FileDescriptor fd);
    2051             
    2052     /**     
    2053      * Writes malloc info data to the specified file descriptor.
    2054      *  
    2055      * @hide
    2056      */     
    2057     public static native void dumpNativeMallocInfo(FileDescriptor fd);    
    

    而另外两个就和虚拟机无关了,通过jni看下native的实现

    frameworks/base/core/jni/android_os_Debug.cpp

    697 /*
    698  * Dump the native heap, writing human-readable output to the specified
    699  * file descriptor.
    700  */
    701 static void android_os_Debug_dumpNativeHeap(JNIEnv* env, jobject,                       
    702     jobject fileDescriptor)
    703 {
    709     ALOGD("Native heap dump starting...\n");
    710     // Formatting of the native heap dump is handled by malloc debug itself.
    711     // See https://android.googlesource.com/platform/bionic/+/master/libc/malloc_debug/README.md#backtrace-heap-dump-format
    712     if (android_mallopt(M_WRITE_MALLOC_LEAK_INFO_TO_FILE, fp.get(), sizeof(FILE*))) {
    713       ALOGD("Native heap dump complete.\n");
    -----------------------------------------------------------------------------
    719 /*
    720  * Dump the native malloc info, writing xml output to the specified
    721  * file descriptor.
    722  */
    723 static void android_os_Debug_dumpNativeMallocInfo(JNIEnv* env, jobject,                 
    724     jobject fileDescriptor)
    725 {
    ......
    731     malloc_info(0, fp.get());
    732 }
    

    712行,heap是用的android_mallopt,731行,mallcinfo是malloc_info。分别看一下

    malloc_common_dynamic.cpp#android_mallopt

    bionic/libc/bionic/malloc_common_dynamic.cpp

    462 extern "C" bool android_mallopt(int opcode, void* arg, size_t arg_size) {
    495   if (opcode == M_WRITE_MALLOC_LEAK_INFO_TO_FILE) {
    500     return WriteMallocLeakInfo(reinterpret_cast<FILE*>(arg));
    501   }
    ---------------------------------------------------------------------------
    428 bool WriteMallocLeakInfo(FILE* fp) {                                                     
    429   void* func = gFunctions[FUNC_WRITE_LEAK_INFO];
    430   bool written = false;
    431   if (func != nullptr) {
    432     written = reinterpret_cast<write_malloc_leak_info_func_t>(func)(fp);
    433   }
    434 
    435   if (!written) {
    436     fprintf(fp, "Native heap dump not available. To enable, run these commands (requires root):\n");
    437     fprintf(fp, "# adb shell stop\n");
    438     fprintf(fp, "# adb shell setprop libc.debug.malloc.options backtrace\n");
    439     fprintf(fp, "# adb shell start\n");
    440     errno = ENOTSUP;
    441   }
    442   return written;
    

    435-440行,没权限写文件时的报错,写在文件里,按照步骤操作再来一遍即可

    本文重心在am命令上,所以不再追踪malloc debug的内容。malloc debug有如下资料推荐

    官方文档:Malloc DebugDebugging Native Memory Use

    其他资料:

    Android内存优化(二)之malloc debug简单介绍与初始化工作

    Android malloc_debug介绍

    malloc debug 内存泄露案例分析

    二、使用

    命令提示

    generic_x86_64:/ # am 
    Activity manager (activity) commands:
    ...
    dumpheap [--user <USER_ID> current] [-n] [-g] <PROCESS> <FILE>
          Dump the heap of a process.  The given <PROCESS> argument may
            be either a process name or pid.  Options are:
          -n: dump native heap instead of managed heap
          -g: force GC before dumping the heap
          --user <USER_ID> | current: When supplying a process name,
              specify user of process to dump; uses current user if not specified.
    

    示例

    generic_x86_64:/ # am dumpheap com.example.myapplication
    File: /data/local/tmp/heapdump-20210730-104505.prof
    Waiting for dump to finish...
    

    需要注意的是,如果native dump,第一次一般需要设置属性,不然dump的文件只有提示信息。操作如下

    Native heap dump not available. To enable, run these commands (requires root):
    # adb shell stop
    # adb shell setprop libc.debug.malloc.options backtrace
    # adb shell start
    

    然后dump之后的文件adb pull下来,类似am profile可以用AndroidStudio的Profiler工具打开。

    三、总结

    am dumpheap命令提供一种获取内存快照的命令行操作入口,可选择java或native。

    java是通过方法VMDebug.dumpHprofData操作虚拟机,而native的是借助Malloc Debug

    Android内存泄漏是个较大的课题,此处仅分析am heapdump命令的实现,具体到debug还需要参照官网和其他的网络资料。官方指导资料如下:

    Inspect your app's memory usage with Memory Profiler

    Debugging Native Memory Use

    Malloc Debug

    本文来自博客园,作者:秋城,转载请注明原文链接:https://www.cnblogs.com/wanghongzhu/p/15080916.html

  • 相关阅读:
    数组下标索引的循环原来可以这样写
    移位运算>>与>>>
    java调用redis的多种方式与心得
    $.ajax传输js数组,spring接收异常
    div背景css样式笔记
    js监听网页页面滑动滚动事件,实现导航栏自动显示或隐藏
    设置系统时区
    安装与配置文本编辑器vim
    添加阿里云数据源
    spring controller获取web前端post数据乱码解决
  • 原文地址:https://www.cnblogs.com/wanghongzhu/p/15080916.html
Copyright © 2020-2023  润新知