OOM Hprof Catch
一. am dumpheap方法
Java进程需要抓取hprof文件的时候可以使用如下命令抓取:
adb shell am dumheap [pid] savePath
1.1 看下流程:
1.Am.java接收到相应的指令后进行分发,会执行相应的方法 runDumpHeap ,然后通过相应的aidl把任务传递到AMS:
frameworks/base/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
int runDumpHeap(PrintWriter pw) throws RemoteException {
if (!mInterface.dumpHeap(process, userId, managed, mallocInfo, runGc, heapFile, fd)) {
err.println("HEAP DUMP FAILED on process " + process);
return -1;
}
return 0;
}
2.AMS 的dumpHeap方法,通过AMS的一系列权限检查后,分发到相应的进程进行真正的hprof dump工作:
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
public boolean dumpHeap(String process, int userId, boolean managed, boolean mallocInfo,
boolean runGc, String path, ParcelFileDescriptor fd) throws RemoteException {
try {
ProcessRecord proc = findProcessLocked(process, userId, "dumpHeap");
proc.thread.dumpHeap(managed, mallocInfo, runGc, path, fd);
return true;
} catch (RemoteException e) {
}
}
3.目标进程的ApplicationThread的dumpHeap方法,这里会把任务交给ActivityThread 的内部类H去处理::
frameworks/base/core/java/android/app/ActivityThread.java
static void handleDumpHeap(DumpHeapData dhd) {
try {
Debug.dumpHprofData(dhd.path, dhd.fd.getFileDescriptor());
} catch (IOException e) {
Slog.w(TAG, "Managed heap dump failed on path " + dhd.path
+ " -- can the process access this path?");
} finally {}
}
4.接下来调用到Debug.java中的实现方法:
frameworks/base/core/java/android/os/Debug.java
/**
* Like dumpHprofData(String), but takes an already-opened
* FileDescriptor to which the trace is written. The file name is also
* supplied simply for logging. Makes a dup of the file descriptor.
*
* Primarily for use by the "am" shell command.
*
* @hide
*/
public static void dumpHprofData(String fileName, FileDescriptor fd)
throws IOException {
VMDebug.dumpHprofData(fileName, fd);
}
5.调用到虚拟机中的Dumpheap方法:
art/runtime/native/dalvik_system_VMDebug.cc
/*
* static void dumpHprofData(String fileName, FileDescriptor fd)
*
* Cause "hprof" data to be dumped. We can throw an IOException if an
* error occurs during file handling.
*/
static void VMDebug_dumpHprofData(JNIEnv* env, jclass, jstring javaFilename, jint javaFd) {
// Only one of these may be null.
if (javaFilename == nullptr && javaFd < 0) {
ScopedObjectAccess soa(env);
ThrowNullPointerException("fileName == null && fd == null");
return;
}
std::string filename;
if (javaFilename != nullptr) {
ScopedUtfChars chars(env, javaFilename);
if (env->ExceptionCheck()) {
return;
}
filename = chars.c_str();
} else {
filename = "[fd]";
}
int fd = javaFd;
hprof::DumpHeap(filename.c_str(), fd, false);
}
6.最终调用hprof.cc中的dump方法:
art/runtime/hprof/hprof.cc
void DumpHeap(const char* filename, int fd, bool direct_to_ddms) {
CHECK(filename != nullptr);
Thread* self = Thread::Current();
// Need to take a heap dump while GC isn't running. See the comment in Heap::VisitObjects().
// Also we need the critical section to avoid visiting the same object twice. See b/34967844
gc::ScopedGCCriticalSection gcs(self,
gc::kGcCauseHprof,
gc::kCollectorTypeHprof);
ScopedSuspendAll ssa(__FUNCTION__, true /* long suspend */);
Hprof hprof(filename, fd, direct_to_ddms);
hprof.Dump();//最终使用DumpToFile输出到文件
}
因此本质上是目标进程的虚拟机在dump这个hprof文件,大致过程如下:
1.2 抓取Java进程OOM时的Hprof文件
ROM开发者可以通过直接调用系统接口Debug.dumpHprofData()在需要dumphprof的地方进行dump操作。
比如我们可以在RuntimeInit.java中在java进程抛异常的时候抓取本进程的hprof文件:
frameworks/base/core/java/com/android/internal/os/RuntimeInit.java
private static class LoggingHandler implements Thread.UncaughtExceptionHandler {
@Override
public void uncaughtException(Thread t, Throwable e) {
// SELF-ADD begin
ApplicationErrorReport.CrashInfo crashInfo = new ApplicationErrorReport.CrashInfo(e);
if("java.lang.OutOfMemoryError".equals(crashInfo.exceptionClassName) {
try {
Debug.dumpHprofData(outPutFileName);
} catch (IOException e) {
e.printStackTrace();
}
}
// END
}
}
当然如上只是简单的描述,是否该做dump需要对具体场景,软件版本等进行判断后再做dumpheap操作。
因为dumpheap的操作是在应用进程的主线程中进行操作,本质上是在该应用进程的虚拟机中进行,dumpheap时应用进程会block住,如过heap文件过大很容易导致应用进程操作界面卡住,如果此时再进行点击或滑动等操作极易再抛出anr等弹窗,用户体验极差。
另外system_server进程抛出OOM异常时同样走到这里,同样可以使用该方法抓取system_server进程OOM时的hprof文件。
此时运行shell命令同样可以,只是跨了众多的进程,这个比较直接。
二. 异常情况的hprof文件抓取
也有异常情况,当OOM发生时去dumpheap,此时内存又回到了低值,抓不到oom之前的现场了。
案例:
发生OOM的是system_server进程,因此重启概率突增,后台监控看来该机型的重启概率飙升了十几倍,相当严重,可是用户反馈看来基本上很少。更奇怪的是什么都没动,两个版本过去后,该问题导致重启概率竟然又下降了10几倍,日了够。
服务器抓取到的日志,在短时间内飙升 从08-10 00:04:44.167 到 08-10 00:04:57.825 10秒多从几十兆飙到500多兆达到峰值,就说你看到这个日志怕不怕:
2.1 日志:
08-09 23:59:31.395 1193 1202 I art : Background sticky concurrent mark sweep GC freed 7270(1366KB) AllocSpace objects, 84(1680KB) LOS objects, 4% free, 57MB/60MB, paused 5.453ms total 53.036ms
08-09 23:59:54.735 1193 1202 I art : Background sticky concurrent mark sweep GC freed 11130(978KB) AllocSpace objects, 39(1300KB) LOS objects, 4% free, 58MB/60MB, paused 5.354ms total 40.800ms
08-10 00:00:00.226 1193 1202 I art : Background sticky concurrent mark sweep GC freed 13139(917KB) AllocSpace objects, 18(1336KB) LOS objects, 3% free, 58MB/60MB, paused 5.051ms total 50.740ms
08-10 00:00:01.685 1193 1202 I art : Background partial concurrent mark sweep GC freed 54242(8MB) AllocSpace objects, 3(1004KB) LOS objects, 6% free, 61MB/65MB, paused 2.338ms total 196.838ms
08-10 00:00:02.091 1193 1202 I art : Background partial concurrent mark sweep GC freed 22138(8MB) AllocSpace objects, 0(0B) LOS objects, 5% free, 63MB/67MB, paused 1.922ms total 242.373ms
08-10 00:00:03.009 1193 1202 I art : Background sticky concurrent mark sweep GC freed 4959(3MB) AllocSpace objects, 30(480KB) LOS objects, 5% free, 63MB/67MB, paused 5.672ms total 42.871ms
08-10 00:00:06.326 5659 5698 I art : Waiting for a blocking GC DisableMovingGc
08-10 00:00:06.328 5659 5698 I art : Starting a blocking GC DisableMovingGc
08-10 00:00:10.989 3138 3150 I art : Background sticky concurrent mark sweep GC freed 3252(3MB) AllocSpace objects, 30(480KB) LOS objects, 24% free, 12MB/16MB, paused 5.453ms total 31.291ms
08-10 00:00:12.127 1193 1202 I art : Background sticky concurrent mark sweep GC freed 1177(2028KB) AllocSpace objects, 0(0B) LOS objects, 1% free, 66MB/67MB, paused 5.414ms total 31.304ms
08-10 00:00:13.469 1193 1202 I art : Background sticky concurrent mark sweep GC freed 345(753KB) AllocSpace objects, 0(0B) LOS objects, 0% free, 66MB/67MB, paused 7.552ms total 22.277ms
08-10 00:00:13.989 1193 1202 I art : Background partial concurrent mark sweep GC freed 12567(6MB) AllocSpace objects, 59(1048KB) LOS objects, 5% free, 63MB/67MB, paused 2.038ms total 184.519ms
08-10 00:00:21.986 1193 1202 I art : Background sticky concurrent mark sweep GC freed 2471(2MB) AllocSpace objects, 0(0B) LOS objects, 3% free, 64MB/67MB, paused 5.281ms total 31.327ms
08-10 00:00:28.702 1193 1202 I art : Background partial concurrent mark sweep GC freed 4937(4MB) AllocSpace objects, 120(2MB) LOS objects, 5% free, 63MB/67MB, paused 3.661ms total 364.700ms
08-10 00:00:30.352 1193 1202 I art : Background sticky concurrent mark sweep GC freed 2912(4MB) AllocSpace objects, 0(0B) LOS objects, 5% free, 63MB/67MB, paused 5.093ms total 40.789ms
08-10 00:00:33.555 1193 1202 I art : Background sticky concurrent mark sweep GC freed 3201(3MB) AllocSpace objects, 0(0B) LOS objects, 4% free, 64MB/67MB, paused 5.120ms total 41.522ms
08-10 00:00:35.927 1193 1202 I art : Background sticky concurrent mark sweep GC freed 4108(2MB) AllocSpace objects, 6(96KB) LOS objects, 4% free, 64MB/67MB, paused 5.201ms total 39.964ms
08-10 00:00:36.504 1193 1202 I art : Background sticky concurrent mark sweep GC freed 3715(2MB) AllocSpace objects, 0(0B) LOS objects, 4% free, 64MB/67MB, paused 5.005ms total 39.263ms
08-10 00:00:37.439 1193 1202 I art : Background sticky concurrent mark sweep GC freed 2013(2MB) AllocSpace objects, 0(0B) LOS objects, 3% free, 64MB/67MB, paused 5.401ms total 42.217ms
08-10 00:00:37.742 1193 1202 I art : Background sticky concurrent mark sweep GC freed 2327(2MB) AllocSpace objects, 0(0B) LOS objects, 3% free, 64MB/67MB, paused 5.479ms total 40.687ms
08-10 00:00:42.013 2648 3700 I art : Starting a blocking GC Explicit
08-10 00:00:42.049 2648 3700 I art : Explicit concurrent mark sweep GC freed 13530(1605KB) AllocSpace objects, 0(0B) LOS objects, 28% free, 10MB/14MB, paused 393us total 35.471ms
08-10 00:00:42.053 2648 3700 I art : Starting a blocking GC Explicit
08-10 00:00:42.072 2648 3700 I art : Explicit concurrent mark sweep GC freed 462(16KB) AllocSpace objects, 0(0B) LOS objects, 28% free, 10MB/14MB, paused 376us total 18.540ms
08-10 00:00:42.315 1193 1202 I art : Background sticky concurrent mark sweep GC freed 19742(1979KB) AllocSpace objects, 7(156KB) LOS objects, 2% free, 65MB/67MB, paused 5.721ms total 84.216ms
08-10 00:00:43.328 1193 1202 I art : Background partial concurrent mark sweep GC freed 60214(3MB) AllocSpace objects, 64(1208KB) LOS objects, 5% free, 63MB/67MB, paused 1.594ms total 216.641ms
08-10 00:00:52.118 1193 1202 I art : Background partial concurrent mark sweep GC freed 38624(3MB) AllocSpace objects, 21(420KB) LOS objects, 5% free, 63MB/67MB, paused 1.699ms total 212.617ms
08-10 00:00:55.946 2004 2014 I art : Background sticky concurrent mark sweep GC freed 8019(353KB) AllocSpace objects, 0(0B) LOS objects, 0% free, 39MB/39MB, paused 5.213ms total 40.251ms
08-10 00:02:15.572 1193 1202 I art : Background partial concurrent mark sweep GC freed 47034(8MB) AllocSpace objects, 53(1364KB) LOS objects, 6% free, 56MB/60MB, paused 1.790ms total 238.573ms
08-10 00:02:18.896 1193 1202 I art : Background partial concurrent mark sweep GC freed 52779(3MB) AllocSpace objects, 8(160KB) LOS objects, 6% free, 56MB/60MB, paused 3.178ms total 382.199ms
08-10 00:04:00.117 1193 1202 I art : Background sticky concurrent mark sweep GC freed 32710(2MB) AllocSpace objects, 33(1220KB) LOS objects, 6% free, 56MB/60MB, paused 5.415ms total 62.678ms
08-10 00:04:44.167 1193 1202 I art : Background partial concurrent mark sweep GC freed 11012(632KB) AllocSpace objects, 8(332KB) LOS objects, 5% free, 67MB/71MB, paused 1.991ms total 241.279ms
08-10 00:04:44.519 1193 1202 I art : Background partial concurrent mark sweep GC freed 3652(391KB) AllocSpace objects, 0(0B) LOS objects, 4% free, 77MB/81MB, paused 1.854ms total 228.374ms
08-10 00:04:44.876 1193 1202 I art : Background partial concurrent mark sweep GC freed 68(7KB) AllocSpace objects, 0(0B) LOS objects, 4% free, 88MB/92MB, paused 1.921ms total 237.600ms
08-10 00:04:45.158 1193 1202 I art : Background partial concurrent mark sweep GC freed 6(192B) AllocSpace objects, 0(0B) LOS objects, 3% free, 99MB/103MB, paused 2.042ms total 176.341ms
08-10 00:04:45.460 1193 1202 I art : Background partial concurrent mark sweep GC freed 18(8KB) AllocSpace objects, 0(0B) LOS objects, 3% free, 111MB/115MB, paused 2.190ms total 190.518ms
08-10 00:04:45.586 1193 1202 I art : Background sticky concurrent mark sweep GC freed 0(0B) AllocSpace objects, 0(0B) LOS objects, 0% free, 115MB/115MB, paused 5.780ms total 37.561ms
08-10 00:04:45.798 1193 1202 I art : Background partial concurrent mark sweep GC freed 36(1648B) AllocSpace objects, 0(0B) LOS objects, 3% free, 122MB/126MB, paused 2.088ms total 211.466ms
08-10 00:04:45.931 1193 1202 I art : Background sticky concurrent mark sweep GC freed 226(18KB) AllocSpace objects, 0(0B) LOS objects, 0% free, 126MB/126MB, paused 11.727ms total 44.764ms
08-10 00:04:46.123 1193 1202 I art : Background partial concurrent mark sweep GC freed 453(24KB) AllocSpace objects, 0(0B) LOS objects, 2% free, 134MB/138MB, paused 2.029ms total 190.864ms
08-10 00:04:46.395 1193 1202 I art : Background partial concurrent mark sweep GC freed 134(7KB) AllocSpace objects, 0(0B) LOS objects, 2% free, 145MB/149MB, paused 1.995ms total 169.401ms
08-10 00:04:46.770 1193 1202 I art : Background partial concurrent mark sweep GC freed 109(5KB) AllocSpace objects, 0(0B) LOS objects, 2% free, 156MB/160MB, paused 2.027ms total 253.147ms
08-10 00:04:47.147 1193 1202 I art : Background partial concurrent mark sweep GC freed 215(22KB) AllocSpace objects, 0(0B) LOS objects, 2% free, 171MB/175MB, paused 1.984ms total 252.140ms
08-10 00:04:47.557 1193 1202 I art : Background partial concurrent mark sweep GC freed 515(65KB) AllocSpace objects, 0(0B) LOS objects, 2% free, 182MB/186MB, paused 2.035ms total 294.901ms
08-10 00:04:47.897 1193 1202 I art : Background partial concurrent mark sweep GC freed 358(31KB) AllocSpace objects, 1(20KB) LOS objects, 2% free, 191MB/195MB, paused 2.134ms total 224.444ms
08-10 00:04:48.191 1193 1202 I art : Background partial concurrent mark sweep GC freed 621(63KB) AllocSpace objects, 0(0B) LOS objects, 1% free, 203MB/207MB, paused 2.055ms total 187.326ms
08-10 00:04:48.514 1193 1202 I art : Background partial concurrent mark sweep GC freed 110(8KB) AllocSpace objects, 0(0B) LOS objects, 1% free, 215MB/219MB, paused 2.196ms total 215.961ms
08-10 00:04:48.790 1193 1202 I art : Background partial concurrent mark sweep GC freed 6(192B) AllocSpace objects, 0(0B) LOS objects, 1% free, 226MB/230MB, paused 2.149ms total 172.089ms
08-10 00:04:49.090 1193 1202 I art : Background partial concurrent mark sweep GC freed 6(192B) AllocSpace objects, 0(0B) LOS objects, 1% free, 237MB/241MB, paused 2.228ms total 173.851ms
08-10 00:04:49.375 1193 1202 I art : Background partial concurrent mark sweep GC freed 104(5KB) AllocSpace objects, 0(0B) LOS objects, 1% free, 248MB/252MB, paused 2.253ms total 173.835ms
08-10 00:04:49.673 1193 1202 I art : Background partial concurrent mark sweep GC freed 13(448B) AllocSpace objects, 0(0B) LOS objects, 1% free, 260MB/264MB, paused 2.259ms total 193.370ms
08-10 00:04:49.970 1193 1202 I art : Background partial concurrent mark sweep GC freed 1104(87KB) AllocSpace objects, 0(0B) LOS objects, 1% free, 271MB/275MB, paused 2.486ms total 186.926ms
08-10 00:04:50.291 1193 1202 I art : Background partial concurrent mark sweep GC freed 259(39KB) AllocSpace objects, 0(0B) LOS objects, 1% free, 283MB/287MB, paused 2.229ms total 207.021ms
08-10 00:04:50.596 1193 1202 I art : Background partial concurrent mark sweep GC freed 35(2048B) AllocSpace objects, 0(0B) LOS objects, 1% free, 294MB/298MB, paused 3.200ms total 192.377ms
08-10 00:04:50.709 1193 1202 I art : Background sticky concurrent mark sweep GC freed 70(3KB) AllocSpace objects, 0(0B) LOS objects, 0% free, 299MB/299MB, paused 5.388ms total 26.256ms
08-10 00:04:50.898 1193 1202 I art : Background partial concurrent mark sweep GC freed 1338(64KB) AllocSpace objects, 0(0B) LOS objects, 1% free, 306MB/310MB, paused 2.209ms total 188.606ms
08-10 00:04:51.200 1193 1202 I art : Background partial concurrent mark sweep GC freed 331(17KB) AllocSpace objects, 0(0B) LOS objects, 1% free, 317MB/321MB, paused 2.329ms total 184.804ms
08-10 00:04:51.323 1193 1202 I art : Background sticky concurrent mark sweep GC freed 1592(139KB) AllocSpace objects, 0(0B) LOS objects, 0% free, 322MB/322MB, paused 8.261ms total 36.921ms
08-10 00:04:51.520 1193 1202 I art : Background partial concurrent mark sweep GC freed 1960(161KB) AllocSpace objects, 0(0B) LOS objects, 1% free, 329MB/333MB, paused 2.538ms total 196.193ms
08-10 00:04:51.883 1193 1202 I art : Background partial concurrent mark sweep GC freed 464(18KB) AllocSpace objects, 0(0B) LOS objects, 1% free, 344MB/348MB, paused 2.162ms total 254.264ms
08-10 00:04:52.207 1193 1202 I art : Background partial concurrent mark sweep GC freed 341(19KB) AllocSpace objects, 0(0B) LOS objects, 1% free, 354MB/358MB, paused 2.173ms total 217.485ms
08-10 00:04:52.579 1193 1202 I art : Background partial concurrent mark sweep GC freed 406(16KB) AllocSpace objects, 0(0B) LOS objects, 1% free, 367MB/371MB, paused 2.258ms total 262.958ms
08-10 00:04:53.007 1193 1202 I art : Background partial concurrent mark sweep GC freed 102(4KB) AllocSpace objects, 0(0B) LOS objects, 1% free, 378MB/382MB, paused 2.208ms total 310.370ms
08-10 00:04:53.327 1193 1202 I art : Background partial concurrent mark sweep GC freed 38(1424B) AllocSpace objects, 0(0B) LOS objects, 1% free, 388MB/392MB, paused 2.212ms total 210.770ms
08-10 00:04:53.767 1193 1202 I art : Background partial concurrent mark sweep GC freed 6(192B) AllocSpace objects, 0(0B) LOS objects, 0% free, 402MB/406MB, paused 2.285ms total 321.039ms
08-10 00:04:53.897 1193 1202 I art : Background sticky concurrent mark sweep GC freed 0(0B) AllocSpace objects, 0(0B) LOS objects, 0% free, 406MB/406MB, paused 5.966ms total 41.389ms
08-10 00:04:54.129 1193 1202 I art : Background partial concurrent mark sweep GC freed 6(192B) AllocSpace objects, 0(0B) LOS objects, 0% free, 413MB/417MB, paused 2.322ms total 230.110ms
08-10 00:04:54.261 1193 1202 I art : Background sticky concurrent mark sweep GC freed 0(0B) AllocSpace objects, 0(0B) LOS objects, 0% free, 417MB/417MB, paused 7.865ms total 43.687ms
08-10 00:04:54.511 1193 1202 I art : Background partial concurrent mark sweep GC freed 298(21KB) AllocSpace objects, 0(0B) LOS objects, 0% free, 424MB/428MB, paused 2.090ms total 249.911ms
08-10 00:04:54.887 1193 1202 I art : Background partial concurrent mark sweep GC freed 48(2624B) AllocSpace objects, 0(0B) LOS objects, 0% free, 435MB/439MB, paused 2.242ms total 258.446ms
08-10 00:04:55.017 1193 1202 I art : Background sticky concurrent mark sweep GC freed 10(752B) AllocSpace objects, 0(0B) LOS objects, 0% free, 440MB/440MB, paused 5.978ms total 40.969ms
08-10 00:04:55.221 1193 1202 I art : Background partial concurrent mark sweep GC freed 6(192B) AllocSpace objects, 0(0B) LOS objects, 0% free, 447MB/451MB, paused 2.312ms total 203.380ms
08-10 00:04:55.366 1193 1202 I art : Background sticky concurrent mark sweep GC freed 0(0B) AllocSpace objects, 0(0B) LOS objects, 0% free, 451MB/451MB, paused 6.244ms total 37.508ms
08-10 00:04:55.627 1193 1202 I art : Background partial concurrent mark sweep GC freed 163(8KB) AllocSpace objects, 0(0B) LOS objects, 0% free, 459MB/463MB, paused 2.290ms total 259.702ms
08-10 00:04:55.759 1193 1202 I art : Background sticky concurrent mark sweep GC freed 0(0B) AllocSpace objects, 0(0B) LOS objects, 0% free, 464MB/464MB, paused 7.253ms total 44.006ms
08-10 00:04:56.123 1193 1202 I art : Background partial concurrent mark sweep GC freed 19(592B) AllocSpace objects, 0(0B) LOS objects, 0% free, 471MB/475MB, paused 5.091ms total 363.359ms
08-10 00:04:56.320 1193 1202 I art : Background sticky concurrent mark sweep GC freed 0(0B) AllocSpace objects, 0(0B) LOS objects, 0% free, 475MB/475MB, paused 5.201ms total 28.116ms
08-10 00:04:56.620 1193 1202 I art : Background partial concurrent mark sweep GC freed 68(4KB) AllocSpace objects, 0(0B) LOS objects, 0% free, 483MB/487MB, paused 4.594ms total 299.701ms
08-10 00:04:56.776 1193 1202 I art : Background sticky concurrent mark sweep GC freed 0(0B) AllocSpace objects, 0(0B) LOS objects, 0% free, 486MB/487MB, paused 5.572ms total 27.013ms
08-10 00:04:56.984 1193 1202 I art : Background partial concurrent mark sweep GC freed 19(60KB) AllocSpace objects, 1(20KB) LOS objects, 0% free, 494MB/498MB, paused 2.273ms total 207.931ms
08-10 00:04:57.117 1193 1202 I art : Background sticky concurrent mark sweep GC freed 1(16B) AllocSpace objects, 0(0B) LOS objects, 0% free, 498MB/498MB, paused 6.765ms total 44.880ms
08-10 00:04:57.335 1193 1202 I art : Background partial concurrent mark sweep GC freed 20(704B) AllocSpace objects, 0(0B) LOS objects, 0% free, 505MB/509MB, paused 3.179ms total 217.057ms
08-10 00:04:57.467 1193 1202 I art : Background sticky concurrent mark sweep GC freed 0(0B) AllocSpace objects, 0(0B) LOS objects, 0% free, 509MB/509MB, paused 8.363ms total 36.710ms
08-10 00:04:57.628 1193 2617 I art : Waiting for a blocking GC Alloc
08-10 00:04:57.825 1193 1202 I art : Clamp target GC heap from 515MB to 512MB
08-10 00:04:57.825 1193 1202 I art : Background partial concurrent mark sweep GC freed 87(6KB) AllocSpace objects, 0(0B) LOS objects, 0% free, 511MB/512MB, paused 3.637ms total 357.088ms
08-10 00:04:57.826 1193 2617 I art : Starting a blocking GC Alloc
08-10 00:04:57.826 1193 2617 I art : Starting a blocking GC Alloc
08-10 00:04:57.854 1193 2617 I art : Alloc sticky concurrent mark sweep GC freed 0(0B) AllocSpace objects, 0(0B) LOS objects, 0% free, 511MB/512MB, paused 3.953ms total 27.850ms
08-10 00:04:57.854 1193 2617 I art : Starting a blocking GC Alloc
08-10 00:04:57.859 1193 2601 I art : Waiting for a blocking GC Alloc
08-10 00:04:58.278 1193 2617 I art : Clamp target GC heap from 515MB to 512MB
08-10 00:04:58.279 1193 2617 I art : Alloc partial concurrent mark sweep GC freed 88(5KB) AllocSpace objects, 0(0B) LOS objects, 0% free, 511MB/512MB, paused 5.126ms total 424.244ms
08-10 00:04:58.279 1193 2617 I art : Starting a blocking GC Alloc
08-10 00:04:58.279 1193 2601 I art : Waiting for a blocking GC Alloc
08-10 00:04:58.634 1193 2617 I art : Clamp target GC heap from 515MB to 512MB
08-10 00:04:58.634 1193 2617 I art : Alloc concurrent mark sweep GC freed 6(208B) AllocSpace objects, 0(0B) LOS objects, 0% free, 511MB/512MB, paused 3.198ms total 354.919ms
。。。
2.2 看日志打印的地方:
art/runtime/gc/heap.cc
void Heap::LogGC(GcCause gc_cause, collector::GarbageCollector* collector) {
const size_t duration = GetCurrentGcIteration()->GetDurationNs();
const std::vector& pause_times = GetCurrentGcIteration()->GetPauseTimes();
// Print the GC if it is an explicit GC (e.g. Runtime.gc()) or a slow GC
// (mutator time blocked >= long_pause_log_threshold_).
bool log_gc = kLogAllGCs || gc_cause == kGcCauseExplicit;
if (!log_gc && CareAboutPauseTimes()) {
// GC for alloc pauses the allocating thread, so consider it as a pause.
log_gc = duration > long_gc_log_threshold_ ||
(gc_cause == kGcCauseForAlloc && duration > long_pause_log_threshold_);
for (uint64_t pause : pause_times) {
log_gc = log_gc || pause >= long_pause_log_threshold_;
}
}
if (log_gc) {
const size_t percent_free = GetPercentFree();
const size_t current_heap_size = GetBytesAllocated();
const size_t total_memory = GetTotalMemory();
std::ostringstream pause_string;
for (size_t i = 0; i < pause_times.size(); ++i) {
pause_string << PrettyDuration((pause_times[i] / 1000) * 1000)
<< ((i != pause_times.size() - 1) ? "," : "");
}
LOG(INFO) << gc_cause << " " << collector->GetName()
<< " GC freed " << current_gc_iteration_.GetFreedObjects() << "("
<< PrettySize(current_gc_iteration_.GetFreedBytes()) << ") AllocSpace objects, "
<< current_gc_iteration_.GetFreedLargeObjects() << "("
<< PrettySize(current_gc_iteration_.GetFreedLargeObjectBytes()) << ") LOS objects, "
<< percent_free << "% free, " << PrettySize(current_heap_size) << "/"
<< PrettySize(total_memory) << ", " << "paused " << pause_string.str()
<< " total " << PrettyDuration((duration / 1000) * 1000);
VLOG(heap) << Dumpable(*current_gc_iteration_.GetTimings());
}
}
从代码中可以看出各个值的意思,其中 current_heap_size 是当前进程分配了的heap大小,的确是从很短的时间内就内存上涨上来了。
现在看下为什么突然上涨上来,因此就需要抓取system_server的hprof文件来确定是哪个对象过大,哪里导致突然分配这么多内存。
于是用上面提到的在RuntimeInit.java处system_server抛OOM异常时抓取system_server的hporf文件,可是每次手机抓取上传到服务器hprof文件大小只有几十兆,恢复了正常。
猜测在system_server OOM crash之前大对象又被清理掉了,头疼,抓不到造事者影子。
2.3 本地复现抓取Hprof文件方法:
于是准备本地复现,跑上monkey进行复现,写个脚本,不断的抓取system_server的hprof文件
编个bin文件监控system_server的heap大小,大体上的想法就是想在运行的手机中抓取到system_server的大的hprof文件,用于查找大对象到底是谁,当然也可以不用这么麻烦,写个python脚本手机连着电脑不断监控抓取就可以,只是后来发现Monkey复现不了,于是准备使用该手机作为日常机器进行操作使用进行复现,需要能在手机自身里运行监控抓取,使用了如下方案,源码如下:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define BUF_SIZE 1024
void modifyMyAdj()
{
char text[100];
sprintf(text, "/proc/%d/oom_score_adj", getpid());
int fd = open(text, O_WRONLY);
if (fd >= 0) {
sprintf(text, "%d", -1000); //让自己不被杀死
write(fd, text, strlen(text));
printf("modify adj sucess!!\n");
} else {
printf("modify adj fail!!\n");
}
close(fd);
}
char* getPidByName(char* task_name, char * pid) {
DIR *dir;
struct dirent *ptr;
FILE *fp;
char filepath[50];//大小随意,能装下cmdline文件的路径即可
char cur_task_name[50];//大小随意,能装下要识别的命令行文本即可
char buf[BUF_SIZE];
dir = opendir("/proc"); //打开路径
if (NULL != dir) {
while ((ptr = readdir(dir)) != NULL) {//循环读取路径下的每一个文件/文件夹
//如果读取到的是"."或者".."则跳过,读取到的不是文件夹名字也跳过
if ((strcmp(ptr->d_name, ".") == 0) || (strcmp(ptr->d_name, "..") == 0))
continue;
if (DT_DIR != ptr->d_type)
continue;
sprintf(filepath, "/proc/%s/status", ptr->d_name);//生成要读取的文件的路径
fp = fopen(filepath, "r");//打开文件
if (NULL != fp) {
if( fgets(buf, BUF_SIZE-1, fp)== NULL ){
fclose(fp);
continue;
}
sscanf(buf, "%*s %s", cur_task_name);
//如果文件内容满足要求则打印路径的名字(即进程的PID)
if (!strcmp(task_name, cur_task_name)){
printf("PID: %s\n", ptr->d_name);
strcpy(pid, ptr->d_name);
}
fclose(fp);
}
}
closedir(dir);//关闭路径
}
return ptr->d_name;
}
void getNameByPid(pid_t pid, char *task_name) {
char proc_pid_path[BUF_SIZE];
char buf[BUF_SIZE];
sprintf(proc_pid_path, "/proc/%d/status", pid);
FILE* fp = fopen(proc_pid_path, "r");
if(NULL != fp){
if( fgets(buf, BUF_SIZE-1, fp)== NULL ){
fclose(fp);
}
fclose(fp);
sscanf(buf, "%*s %s", task_name);
}
}
unsigned getRssByPid(pid_t pid) {
char proc_pid_stat[BUF_SIZE];
char buf[BUF_SIZE];
sprintf(proc_pid_stat, "/proc/%d/stat", pid);
FILE* fp = fopen(proc_pid_stat, "r");
char *temp;
int i = 0;
if(NULL != fp){
if( fgets(buf, BUF_SIZE-1, fp)== NULL ){
fclose(fp);
}
fclose(fp);
temp = strtok(buf," ");
while(temp)
{
i++;
printf("%d :%s \n",i,temp);
if(i == 24) {
return strtoul(temp, 0, 10)*4;
}
temp = strtok(NULL," ");
}
}
return 0;
}
#define RSS_STEP 50*1024 //50M
#define HPROF_FILE_DIR "/data/local/tmp/"
int main(int argc, char *argv[]) {
modifyMyAdj();
unsigned rss_threshold = 200 * 1024;
char task_name[50];
char *pid = (char*)calloc(10,sizeof(char));
strcpy(task_name, "system_server");
getPidByName(task_name, pid);
char target_file[128];
char dumpheap_cmd[256];
system("mkdir -p " HPROF_FILE_DIR);
printf("OOMMonitor start monitor system_server rss!\n");
while (true ){
unsigned rss = getRssByPid(atoi(pid));
printf("OOMMonitor unsigned rss = %u !\n",rss);
if (rss==0)
{
printf("OOMMonitor Error read system_server pid , system_server may reboot!\n");
getPidByName(task_name, pid);
continue;
}
if (rss>rss_threshold){
sprintf(target_file, "%sss_oom_%u.hprof",HPROF_FILE_DIR,rss);
sprintf(dumpheap_cmd, "am dumpheap %s %s",pid,target_file);
system(dumpheap_cmd);
printf("OOMMonitor dumpheap finished: %s\n",target_file);
rss_threshold += RSS_STEP;
}
if (rss>500 * 1024)
{
printf("%s\n","OOMMonitor system_server oom!");
sprintf(target_file, "%sss_oom_%u.hprof",HPROF_FILE_DIR,rss);
sprintf(dumpheap_cmd, "am dumpheap %s %s",pid,target_file);
system(dumpheap_cmd);
system("bugreport > " HPROF_FILE_DIR "bug.txt");
system("am broadcast -a com.XXX.bugreport.service.action.CONFIRM_DIALOG");
break;
}
sleep(2);
}
return 0;
}
mk文件,在源码下编译出该bin文件,OOMMonitor:
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES:= \
monitor.cpp
LOCAL_MODULE:= OOMMonitor
include $(BUILD_EXECUTABLE)
push到system/bin/下,给予OOMMonitor运行的权限,并跑起来,也可以更改rc文件让其开机自动就跑起来。
adb shell OOMMonitor &
跑起monkey,一般如果大概率OOM的案例跑跑应该都可以很快复现,可这次怎么跑都不复现,本地手机又少,只有2,3台,日乐购。
因此最终还是思考需要从用户机器上抓取hprof。
2.3 在线抓取用户机器上的hprof文件:
既然最终OOM 抛异常Crash之前大对象就不见了,那就在system_server heap增大的过程中对其heap进行dump,还能跑的了?我靠
于是准备如下这么干:
通过跑shell脚本来抓取hprof文件
将system_server的pid设置到属性里脚本直接读取属性值得到system_server的pid
#!/system/bin/sh
ss_pid=`getprop sys.XXX.dumpheap`
mkdir -p /data/local/hprof/exceed/
am dumpheap $ss_pid /data/local/hprof/exceed/system.hprof
echo "oom sh dumpheap done!"
chown -R system:system /data/local/hprof/
更该rc文件,当属性一旦被设置后就跑起该shell脚本:
on property:sys.XXX.dumpheap=*
exec u:r:dumpstate:s0 -- /system/bin/sh /init.XXX.dumpheap.sh
设置属性的地方就放在上面gclog打印的地方,当 current_heap_size 大于450M的时候就去抓取设置熟悉,执行shell脚本,抓取hprof文件:
diff --git a/runtime/gc/heap.cc b/runtime/gc/heap.cc
index 82c160f..053f459 100644
--- a/runtime/gc/heap.cc
+++ b/runtime/gc/heap.cc
@@ -23,6 +23,10 @@
#include
#include // For GC verification.
#include
+// XXX Add : Start
+#include
+#include
+// End
#include "art_field-inl.h"
#include "base/allocator.h"
@@ -74,6 +78,12 @@
#include "handle_scope-inl.h"
#include "thread_list.h"
#include "well_known_classes.h"
+
+#ifdef HAVE_ANDROID_OS
+extern "C" {
+ int property_set(const char* name, const char* value);
+}
+#endif
namespace art {
@@ -215,7 +225,8 @@
kGcCountRateMaxBucketCount),
backtrace_lock_(nullptr),
seen_backtrace_count_(0u),
- unique_backtrace_count_(0u) {
+ unique_backtrace_count_(0u),
+ need_dump_heap_(0u) {// XXX Add
if (VLOG_IS_ON(heap) || VLOG_IS_ON(startup)) {
LOG(INFO) << "Heap() entering";
}
@@ -2522,6 +2533,9 @@
<< percent_free << "% free, " << PrettySize(current_heap_size) << "/"
<< PrettySize(total_memory) << ", " << "paused " << pause_string.str()
<< " total " << PrettyDuration((duration / 1000) * 1000);
+ // XXX Add : Start
+ CheckConcurrentHeapSize(current_heap_size);
+ // End
VLOG(heap) << Dumpable(*current_gc_iteration_.GetTimings());
}
}
@@ -3776,6 +3790,30 @@
}
}
}
-
+// XXX Add : Start
+#ifdef HAVE_ANDROID_OS
+void Heap::CheckConcurrentHeapSize(size_t current_heap_size) {
+ if(current_heap_size < 450 *1024 *1024) {
+ return;
+ }
+ if(!need_dump_heap_.CompareExchangeStrongSequentiallyConsistent(0,1)) {
+ return;
+ }
+ std::string current_cmd_line;
+ std::string propertyName = "sys.XXX.dumpheap";
+ if(ReadFileToString("/proc/self/cmdline", ¤t_cmd_line)) {
+ int ret = strcmp(current_cmd_line.c_str(), "system_server");
+ if(ret == 0 ) {
+ ret = property_set(propertyName.c_str(),std::to_string(getpid()).c_str());
+ LOG(WARNING) << "system_server oomevent ,set prop ret: " << ret;
+ }
+ }
+}
+#else
+void Heap::CheckConcurrentHeapSize(__attribute__((unused)) size_t new_num_bytes_allocated) {
+ return;
+}
+#endif
+// End
} // namespace gc
} // namespace art
diff --git a/runtime/gc/heap.h b/runtime/gc/heap.h
index 026795c..75c97c7 100644
--- a/runtime/gc/heap.h
+++ b/runtime/gc/heap.h
@@ -891,6 +891,9 @@
// GC stress mode attempts to do one GC per unique backtrace.
void CheckGcStressMode(Thread* self, mirror::Object** obj)
SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+ // XXX Add : Start
+ void CheckConcurrentHeapSize(size_t current_heapsize);
+ // End
// All-known continuous spaces, where objects lie within fixed bounds.
std::vector continuous_spaces_;
@@ -1205,7 +1208,9 @@
Atomic unique_backtrace_count_;
// Stack trace hashes that we already saw,
std::unordered_set seen_backtraces_ GUARDED_BY(backtrace_lock_);
-
+ // XXX Add : Start
+ AtomicInteger need_dump_heap_;
+ // End
friend class CollectorTransitionTask;
friend class collector::GarbageCollector;
friend class collector::MarkCompact;
之后再通过我厂的log收集系统得到log和hprof文件,hprof需要压缩后上传,当然这之后都是建立在已有的log收集系统之上,hprof有将近500M,压缩完后大概只有30-50M。
应该可以直接在art打日志的地方调用hprof:dump()方法实现,就不用通过上面这种方式,跨了好几个进程,只是我在试直接dump的时候有发生系统卡住的情况,毕竟是虚拟机里改动,影响会很大,决定采用上面方式,虽然绕了点,但是比较完整成熟。
因为抓取大的hprof会影响用户体验,因此我们选择携带如上change打包,新ota升级的用户手机里发生问题时即可以抓取hprof文件并上传上来,再下一个版本就不携带该change了,只影响该一个版本,尽量将影响控制到最小。
呵呵,终于抓到了这次。。。
2.4 OOM问题分析
看下抓到的hprof文件终于可以定位问题了:
hprof中 long型数据Processstats对象占了400多兆,大头呀,数量众多,都是32768字节,这个东西是怎么来的?
在异常的logcat中我们看到一些相关的异常日志:
09-08 22:14:53.058 1169 2675 E ProcessStatsService: java.lang.OutOfMemoryError: Failed to allocate a 32784 byte allocation with 16256 free bytes and 11KB until OOM
09-08 22:14:53.058 1169 2675 E ProcessStatsService: at com.android.internal.app.ProcessStats.readFromParcel(ProcessStats.java:1751)
09-08 22:14:53.058 1169 2675 E ProcessStatsService: at com.android.internal.app.ProcessStats.read(ProcessStats.java:1696)
09-08 22:14:53.058 1169 2675 E ProcessStatsService: at com.android.server.am.ProcessStatsService.readLocked(ProcessStatsService.java:313)
09-08 22:14:53.058 1169 2675 E ProcessStatsService: at com.android.server.am.ProcessStatsService.getStatsOverTime(ProcessStatsService.java:530)
09-08 22:14:53.058 1169 2675 E ProcessStatsService: at com.android.server.am.ProcessStatsService.dumpAggregatedStats(ProcessStatsService.java:583)
09-08 22:14:53.058 1169 2675 E ProcessStatsService: at com.android.server.am.ProcessStatsService.dumpInner(ProcessStatsService.java:909)
09-08 22:14:53.058 1169 2675 E ProcessStatsService: at com.android.server.am.ProcessStatsService.dump(ProcessStatsService.java:651)
09-08 22:14:53.058 1169 2675 E ProcessStatsService: at android.os.Binder.dump(Binder.java:331)
09-08 22:14:53.058 1169 2675 E ProcessStatsService: at android.os.Binder.onTransact(Binder.java:297)
09-08 22:14:53.058 1169 2675 E ProcessStatsService: at com.android.internal.app.IProcessStats$Stub.onTransact(IProcessStats.java:81)
09-08 22:14:53.058 1169 2675 E ProcessStatsService: at com.android.server.am.ProcessStatsService.onTransact(ProcessStatsService.java:120)
09-08 22:14:53.058 1169 2675 E ProcessStatsService: at android.os.Binder.execTransact(Binder.java:460)
该调用栈一般是使用dumpsys procstats服务调用,查看整个代码发现只有我厂自己的性能监控软件会去定时的调用dump procstats服务来获取进程的运行数据。
抓上来的日志看到调用栈的线程 1169 2675的binder trasaction信息,该调用是从进程31045调用过来:
proc 31045
thread 31045: l 10
outgoing transaction 9933905: 0000000000000000 from 31045:31045 to 1169:2675 code 5f444d50 flags 10 pri 0 r1 node 4001 size 72:8 data 0000000000000000
而ps信息中可以看到最终是由我厂的com.XXX.XXXdaemon性能监控程序调用的,该程序一般夜里收集性能数据,用户都不知道,当然用户反馈少。
------ ps(-t,-p,-P) ------
USER PID PPID VSIZE RSS PRIO NICE RTPRI SCHED PCY WCHAN PC NAME
system 31045 2610 16616 1736 20 0 0 0 fg binder_thr 0000000000 S dumpsys //父进程是2610
system 2610 533 1685660 50384 20 0 0 0 fg SyS_epoll_ 0000000000 S com.XXX.XXXdaemon //2610是我厂性能监控程序
但是看了调用的性能监控程序,调用的频率并不高,一天调用一次,看了下dumpsys procstats 的原理,实际上是应用进程的操作的生命周期里,AMS会不断的向/data/system/procstats/下 更新进程的运行数据:
当然dumpsys procstats时即是从该文件夹下读取数据的过程。
查看代码,该代码是m机型上才有,m之上的已经没有了:
frameworks/base/core/java/com/android/internal/app/ProcessStats.java
final int NLONGS = in.readInt();
final int NEXTLONG = in.readInt();
mLongs.clear();
for (int i=0; i<(NLONGS-1); i++) {//日志中未打印该值,无法知道到底多大,但应该是很大导致分配new longs 过多而oom,
for (int i=0; i<(NLONGS-1); i++) {
while (i >= mLongs.size()) {
mLongs.add(new long[LONGS_SIZE]);
}
readCompactedLongArray(in, version, mLongs.get(i), LONGS_SIZE);
}
long[] longs = new long[LONGS_SIZE];
mNextLong = NEXTLONG;
readCompactedLongArray(in, version, longs, NEXTLONG);//日志中有报NEXTLONG太大而抛异常 dump procstats 失败,但dump 方法 有try catch这种异常而未影响system_server进程
最终因为new long[LONGS_SIZE])分配过多而oom
这里的LONGS_SIZE值是4096,long型占用8字节,这里mLongs中的元素是LONGS_SIZE的long型数组,则new一个的size正好就是32768。
猜测/data/system/procstats/下的文件有异常,再看hprof文件,的确mLongs的size值很大:
hprof1:
hprof2:
NLONGS是从ProcessStats对象中的成员变量mLongs的size获取到的,从hprof文件看来这个值应该相当的大,这么大的值,上面代码中一个for循环分配,很快heap就飙上去了,这里就是根本原因。
/data/system/procstats下的文件会不断的更新掉,如果异常文件被更新掉了也就不会再出现OOM了,怪不得又OOM的概率两个版本后又下降了。
寻找代码为看出哪里有导致procstats写入异常,而且当时OOM的概率已经下降,异常文件已经被更新掉,因此采取workarround方法,避免之后再遇到异常文件.
workaround方式,对NSLONG进行判断,若太大则不再去分配,放弃本次dumpsys procstats操作即可。
if (NLONGS > 4000) {
Slog.i(TAG, "Record size maybe too large , disgard to avoid oom ! NLONGS: " + NLONGS);
mReadError = "bad NLONGS count: " + NLONGS;
return;
}
三. OOM异常分析总结
分析OOM最主要需要看是哪里有内存泄漏,通常OOM的时候其他进程或线程有分配内存的时候就很容易躺枪,造成crash的调用栈并不是根本原因,只是躺枪,分析OOM最主要的利器就是hprof文件了,能有效抓取到现场的hprof基本上就已经成功一大半了。
- 本地容易复现:
采用脚本监控进程内存的情况,达到阈值及手动抓取,或脚本自动抓取,python脚本shell脚本均可。
应用的话可以使用leakcanery进行复现,很简单且明了。也可以复写unCatchExceptionHandler,抛异常之前抓取下hprof文件。 - 本地不易复现:
想办法在线抓取,即在该抓hprof的地方通过调用Debug接口的方式或者跑属性服务脚本的方式进行监控及抓取,上传上来分析。