代码基于android11。am命令的实现见debug:am、cmd命令。书接上文,
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行
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行入参处理
另外文件名不指定的话,默认路径与文件名是/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对端跟踪
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装参数
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行,抓完了的回调通知。
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是操作了虚拟机
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
。分别看一下
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简单介绍与初始化工作
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