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-ipc
、profile
一样,这里也是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 Debug、Debugging Native Memory Use
其他资料:
Android内存优化(二)之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还需要参照官网和其他的网络资料。官方指导资料如下: