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

上一篇:heapq — Heap queue algorithm(堆队列,优先队列)


下一篇:剑指 Offer 40. 最小的k个数