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-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還需要參照官網和其他的網路資料。官方指導資料如下: