graph TB
demo-->java-oom
java-oom-->koom-kwailinker
java-oom-->koom-xhook
java-oom-->koom-shark
graph TB
LeakDetector-->ActivityLeakDetector
LeakDetector-->NativeAllocationRegistryLeakDetector
LeakDetector-->WindowLeakDetector
LeakDetector-->BitmapLeakDetector
LeakDetector-->FragmentLeakDetector
/**
* KOOM entry point, make sure be called in the main thread!
*
* @param application application needed
*/
public static void init(Application application) {
if (koom == null) {
koom = new KOOM(application);
}
koom.start();
}
private KOOM(Application application) {
internal = new KOOMInternal(application);
}
public KOOMInternal(Application application) {
KUtils.startup();
buildConfig(application);
heapDumpTrigger = new HeapDumpTrigger();
heapAnalysisTrigger = new HeapAnalysisTrigger();
ProcessLifecycleOwner.get().getLifecycle().addObserver(heapAnalysisTrigger);
}
private void buildConfig(Application application) {
//setApplication must be the first
KGlobalConfig.setApplication(application);
KGlobalConfig.setKConfig(KConfig.defaultConfig());
}
implements KTrigger
public HeapDumpTrigger() {
monitorManager = new MonitorManager();
monitorManager.addMonitor(new HeapMonitor());
heapDumper = new ForkJvmHeapDumper();
}
public MonitorManager() {
monitors = new ArrayList<>();
monitorThread = new MonitorThread();
}
public void addMonitor(Monitor monitor) {
monitors.add(monitor);
}
public MonitorThread() {
thread = new HandlerThread("MonitorThread");
thread.start();
handler = new Handler(thread.getLooper());
}
/* HeapMonitor watch JVM heap running info,
* and trigger when heap is over threshold
* several times as HeapThreshold set. */
class HeapMonitor implements Monitor
@Override
public boolean isTrigger() {
return currentTimes >= heapThreshold.overTimes();
}
// A jvm hprof dumper which use fork and don’t block main process.
public ForkJvmHeapDumper() {
soLoaded = KGlobalConfig.getSoLoader().loadLib("koom-java");
if (soLoaded) {
initForkDump();
}
}
implements KTrigger
public void start() {
internal.start();
}
public void start() {
HandlerThread koomThread = new HandlerThread("koom");
koomThread.start();
koomHandler = new Handler(koomThread.getLooper());
startInKOOMThread();
}
private void startInKOOMThread() {
koomHandler.postDelayed(this::startInternal, KConstants.Perf.START_DELAY);
}
private void startInternal() {
heapDumpTrigger.setHeapDumpListener(this);
heapAnalysisTrigger.setHeapAnalysisListener(this);
ReanalysisChecker reanalysisChecker = new ReanalysisChecker();
if (reanalysisChecker.detectReanalysisFile() != null) {
KLog.i(TAG, "detected reanalysis file");
heapAnalysisTrigger.trigger(TriggerReason.analysisReason(TriggerReason.AnalysisReason.REANALYSIS));
return;
}
heapDumpTrigger.startTrack();
}
…
@Override
public void startTrack() {
monitorManager.start();
monitorManager.setTriggerListener((monitorType, reason) -> {
trigger(reason);
return true;
});
}
monitorManager.start
public void start() {
monitorThread.start(monitors);
}
public void start(List<Monitor> monitors) {
stop = false;
List<Runnable> runnables = new ArrayList<>();
for (Monitor monitor : monitors) {
monitor.start();
runnables.add(new MonitorRunnable(monitor));
}
for (Runnable runnable : runnables) {
handler.post(runnable);
}
}
@Override
public void start() {
started = true;
if (heapThreshold == null) {
heapThreshold = KGlobalConfig.getHeapThreshold();
}
}
class MonitorRunnable implements Runnable {
@Override
public void run() {
if (monitor.isTrigger()) {
stop = monitorTriggerListener.onTrigger(monitor.monitorType(), monitor.getTriggerReason());
}
if (!stop) {
handler.postDelayed(this, monitor.pollInterval());
}
}
}
…
@Override
public void trigger(TriggerReason reason) {
if (triggered) {
KLog.e(TAG, "Only once trigger!");
return;
}
triggered = true;
monitorManager.stop();
KLog.i(TAG, "trigger reason:" + reason.dumpReason);
if (heapDumpListener != null) {
heapDumpListener.onHeapDumpTrigger(reason.dumpReason);
}
try {
doHeapDump(reason.dumpReason);
} catch (Exception e) {
KLog.e(TAG, "doHeapDump failed");
e.printStackTrace();
if (heapDumpListener != null) {
heapDumpListener.onHeapDumpFailed();
}
}
KVData.addTriggerTime(KGlobalConfig.getRunningInfoFetcher().appVersion());
}
public void doHeapDump(TriggerReason.DumpReason reason) {
KLog.i(TAG, "doHeapDump");
KHeapFile.getKHeapFile().buildFiles();
HeapAnalyzeReporter.addDumpReason(reason);
HeapAnalyzeReporter.addDeviceRunningInfo();
boolean res = heapDumper.dump(KHeapFile.getKHeapFile().hprof.path);
if (res) {
heapDumpListener.onHeapDumped(reason);
} else {
KLog.e(TAG, "heap dump failed!");
heapDumpListener.onHeapDumpFailed();
KHeapFile.delete();
}
}
@Override
public boolean dump(String path) {
boolean dumpRes = false;
try {
int pid = trySuspendVMThenFork();
if (pid == 0) {
Debug.dumpHprofData(path);
KLog.i(TAG, "notifyDumped:" + dumpRes);
//System.exit(0);
exitProcess();
} else {
resumeVM();
dumpRes = waitDumping(pid);
KLog.i(TAG, "hprof pid:" + pid + " dumped: " + path);
}
} catch (IOException e) {
e.printStackTrace();
KLog.e(TAG, "dump failed caused by IOException!");
}
return dumpRes;
}
/**
* First do suspend vm, then do fork.
* @return result of fork
*/
private native int trySuspendVMThenFork();//C层对应_ZN3art3Dbg9SuspendVMEv这个符号方法调用以及fork系统调用
/**
* Resume the VM.
*/
private native void resumeVM();//对应的方法符号为“_ZN3art3Dbg8ResumeVMEv”
private boolean waitDumping(int pid) {
waitPid(pid);
return true;
}
/**
* Wait process exit.
*
* @param pid waited process.
*/
private native void waitPid(int pid);//系统调用
KOOMInternal
@Override
public void onHeapDumped(TriggerReason.DumpReason reason) {
KLog.i(TAG, "onHeapDumped");
changeProgress(KOOMProgressListener.Progress.HEAP_DUMPED);
//Crash cases need to reanalyze next launch and not do analyze right now.
if (reason != TriggerReason.DumpReason.MANUAL_TRIGGER_ON_CRASH) {
heapAnalysisTrigger.startTrack();
} else {
KLog.i(TAG, "reanalysis next launch when trigger on crash");
}
}
@Override
public void startTrack() {
KTriggerStrategy strategy = strategy();
if (strategy == KTriggerStrategy.RIGHT_NOW) {
trigger(TriggerReason.analysisReason(TriggerReason.AnalysisReason.RIGHT_NOW));
}
}
@Override
public void trigger(TriggerReason triggerReason) {
doAnalysis(KGlobalConfig.getApplication());
public void doAnalysis(Application application) {
HeapAnalyzeService.runAnalysis(application, heapAnalysisListener);
}
public static void runAnalysis(Application application,
HeapAnalysisListener heapAnalysisListener) {
KLog.i(TAG, "runAnalysis startService");
Intent intent = new Intent(application, HeapAnalyzeService.class);
IPCReceiver ipcReceiver = buildAnalysisReceiver(heapAnalysisListener);
intent.putExtra(KConstants.ServiceIntent.RECEIVER, ipcReceiver);
KHeapFile heapFile = KHeapFile.getKHeapFile();
intent.putExtra(KConstants.ServiceIntent.HEAP_FILE, heapFile);
application.startService(intent);
}
private static IPCReceiver buildAnalysisReceiver(HeapAnalysisListener heapAnalysisListener) {
return new IPCReceiver(new IPCReceiver.ReceiverCallback() {
@Override
public void onSuccess() {
KLog.i(TAG, "IPC call back, heap analysis success");
heapAnalysisListener.onHeapAnalyzed();
}
@Override
public void onError() {
KLog.i(TAG, "IPC call back, heap analysis failed");
heapAnalysisListener.onHeapAnalyzeFailed();
}
});
}
@Override
protected void onHandleIntent(Intent intent) {
boolean res = false;
beforeAnalyze(intent);
res = doAnalyze();
if (ipcReceiver != null) {
ipcReceiver.send(res ? IPCReceiver.RESULT_CODE_OK
: IPCReceiver.RESULT_CODE_FAIL, null);
}
}
/** run in the heap_analysis process */
private boolean doAnalyze() {
return heapAnalyzer.analyze();
}
public KHeapAnalyzer(KHeapFile heapFile) {
leaksFinder = new SuspicionLeaksFinder(heapFile.hprof);
}
public boolean analyze() {
KLog.i(TAG, "analyze");
Pair<List<ApplicationLeak>, List<LibraryLeak>> leaks = leaksFinder.find();
if (leaks == null) {
return false;
}
//Add gc path to report file.
HeapAnalyzeReporter.addGCPath(leaks, leaksFinder.leakReasonTable);
//Add done flag to report file.
HeapAnalyzeReporter.done();
return true;
}
public SuspicionLeaksFinder(KHeapFile.Hprof hprof) {
leakingObjects = new HashSet<>();
leakDetectors = new ArrayList<>();
computeGenerations = new HashSet<>();
this.hprofFile = hprof;
}
public Pair<List<ApplicationLeak>, List<LibraryLeak>> find() {
boolean indexed = buildIndex();//just as shark in leakcannary
if (!indexed) {
return null;
}
initLeakDetectors();
findLeaks();
return findPath();
}
……
private void initLeakDetectors() {
addDetector(new ActivityLeakDetector(heapGraph));
addDetector(new FragmentLeakDetector(heapGraph));
addDetector(new BitmapLeakDetector(heapGraph));
addDetector(new NativeAllocationRegistryLeakDetector(heapGraph));
addDetector(new WindowLeakDetector(heapGraph));
ClassHierarchyFetcher.initComputeGenerations(computeGenerations);
leakReasonTable = new HashMap<>();
}
private void addDetector(LeakDetector leakDetector) {
leakDetectors.add(leakDetector);
computeGenerations.add(leakDetector.generation());
}
//use for findLeaks, corresponding to find the leak instance during monitoring in leakcanary
public void findLeaks() {
KLog.i(TAG, "start find leaks");
//遍历镜像的所有instance
Sequence<HeapObject.HeapInstance> instances = heapGraph.getInstances();
Iterator<HeapObject.HeapInstance> instanceIterator = instances.iterator();
while (instanceIterator.hasNext()) {
HeapObject.HeapInstance instance = instanceIterator.next();
if (instance.isPrimitiveWrapper()) {
continue;
}
ClassHierarchyFetcher.process(instance.getInstanceClassId(),
instance.getInstanceClass().getClassHierarchy());
for (LeakDetector leakDetector : leakDetectors) {
if (leakDetector.isSubClass(instance.getInstanceClassId())
&& leakDetector.isLeak(instance)) {
ClassCounter classCounter = leakDetector.instanceCount();
if (classCounter.leakInstancesCount <=
SAME_CLASS_LEAK_OBJECT_GC_PATH_THRESHOLD) {
leakingObjects.add(instance.getObjectId());
leakReasonTable.put(instance.getObjectId(), leakDetector.leakReason());
}
}
}
}
//关注class和对应instance数量,加入json
HeapAnalyzeReporter.addClassInfo(leakDetectors);
findPrimitiveArrayLeaks();
findObjectArrayLeaks();
}
public Pair<List<ApplicationLeak>, List<LibraryLeak>> findPath() {
KLog.i(TAG, "findPath object size:" + leakingObjects.size());
HeapAnalyzer.FindLeakInput findLeakInput = new HeapAnalyzer.FindLeakInput(heapGraph,
AndroidReferenceMatchers.Companion.getAppDefaults(),
false, Collections.emptyList());
kotlin.Pair<List<ApplicationLeak>, List<LibraryLeak>> pair =
new HeapAnalyzer(step -> KLog.i(TAG, "step:" + step.name()))
.findLeaks(findLeakInput, leakingObjects, true);
return new Pair<>((List<ApplicationLeak>) pair.getFirst(),
(List<LibraryLeak>) pair.getSecond());
}
…
public static String getReportDir() {
return reportDir = getRootDir() + File.separator + REPORT_DIR;
}
public static String getHprofDir() {
return hprofDir = getRootDir() + File.separator + HPROF_DIR;
}
//* See KHeapFile, each contains a pair of hprof and report which file prefix is same.
public KHeapFile detectReanalysisFile() {
File reportDir = new File(KGlobalConfig.getReportDir());
File[] reports = reportDir.listFiles();
if (reports == null) {
return null;
}
for (File report : reports) {
HeapReport heapReport = loadFile(report);
if (analysisNotDone(heapReport)) {
if (!overReanalysisMaxTimes(heapReport)) {
KLog.i(TAG, "find reanalyze report");
return buildKHeapFile(report);
} else {
KLog.e(TAG, "Reanalyze " + report.getName() + " too many times");
//Reanalyze too many times, and the hporf is abnormal, so delete them.
File hprof = findHprof(getReportFilePrefix(report));
if (hprof != null) {
hprof.delete();
}
report.delete();
}
}
}
return null;
}
private KHeapFile buildKHeapFile(File report) {
String reportPrefix = getReportFilePrefix(report);
File hprof = findHprof(reportPrefix);
if (hprof != null) {
return KHeapFile.buildInstance(hprof, report);
} else {
KLog.e(TAG, "Reanalyze hprof file not found!");
report.delete();
}
return null;
}
implements Parcelable
public Hprof hprof;
public Report report;
public static KHeapFile buildInstance(File hprof, File report) {
kHeapFile = getKHeapFile();
kHeapFile.hprof = new Hprof(hprof.getAbsolutePath());
kHeapFile.report = new Report(report.getAbsolutePath());
return kHeapFile;
}
//corresponding to shark in leakcanary
//增加同类对象数量阈值剪枝
public fun FindLeakInput.findLeaks(leakingObjectIds: Set<Long>, enableSameInstanceThreshold: Boolean): Pair<List<ApplicationLeak>, List<LibraryLeak>> {
SharkLog.d { "start findLeaks" }
val pathFinder = PathFinder(graph, listener, referenceMatchers, enableSameInstanceThreshold)
val pathFindingResults =
pathFinder.findPathsFromGcRoots(leakingObjectIds, computeRetainedHeapSize)
SharkLog.d { "Found ${leakingObjectIds.size} retained objects" }
return buildLeakTraces(pathFindingResults)
}
// key: current class ; value: subclass according to every detector class
private Map<Long, List<ClassGeneration>> classGenerations;
public static long getIdOfGeneration(long classId, int generation) {
List<ClassGeneration> generations = getClassGenerations().get(classId);
if (generations == null) {
return 0;
}
for (ClassGeneration classGeneration : generations) {
if (classGeneration.generation == generation) {
return classGeneration.id;
}
}
return 0;
}
/**
* LeakDetector try's to find the detect class's leaked instance and first we need to judge
* whether the object's class is sub class of LeakDetector's detect class.
* <p>
* See generation(), if the instance's class is sub class of detect class, then it's
* 'Generation' position class id is same with the detect class.
* <p>
* Using generation to judge subclass instance is the relatively more efficient way.
*
* @param classId instance class id
* @return whether the instance class id is sub class of this leak detector's detect class.
*/
boolean isSubClass(long classId) {
return ClassHierarchyFetcher.getIdOfGeneration(classId, generation()) == classId();
}
JNIEXPORT jboolean JNICALL
Java_com_kwai_koom_javaoom_dump_ForkJvmHeapDumper_initForkDump(JNIEnv *env, jobject jObject) {
return initForkVMSymbols();
}
bool initForkVMSymbols() {
bool res = false;
void *libHandle = kwai::linker::DlFcn::dlopen("libart.so", RTLD_NOW);
suspendVM = (void (*)())kwai::linker::DlFcn::dlsym(libHandle, "_ZN3art3Dbg9SuspendVMEv");
resumeVM = (void (*)())kwai::linker::DlFcn::dlsym(libHandle, "_ZN3art3Dbg8ResumeVMEv");
kwai::linker::DlFcn::dlclose(libHandle);
return suspendVM != nullptr && resumeVM != nullptr;
}
JNIEXPORT void JNICALL
Java_com_kwai_koom_javaoom_dump_ForkJvmHeapDumper_exitProcess(JNIEnv *env, jobject jObject) {
_exit(0);
}
JNIEXPORT void JNICALL Java_com_kwai_koom_javaoom_dump_ForkJvmHeapDumper_waitPid(JNIEnv *env,
jobject jObject,
jint pid) {
int status;
waitpid(pid, &status, 0);
}
JNIEXPORT void JNICALL
Java_com_kwai_koom_javaoom_dump_StripHprofHeapDumper_initStripDump(JNIEnv *env, jobject jObject) {
hprofFd = -1;
hprofName = nullptr;
isDumpHookSucc = false;
xhook_enable_debug(0);
/**
*
* android 7.x,write方法在libc.so中
* android 8-9,write方法在libart.so中
* android 10,write方法在libartbase.so中
* libbase.so是一个保险操作,防止前面2个so里面都hook不到(:
*
* android 7-10版本,open方法都在libart.so中
* libbase.so与libartbase.so,为保险操作
*/
xhook_register("libart.so", "open", (void *)hook_open, nullptr);
xhook_register("libbase.so", "open", (void *)hook_open, nullptr);
xhook_register("libartbase.so", "open", (void *)hook_open, nullptr);
xhook_register("libc.so", "write", (void *)hook_write, nullptr);
xhook_register("libart.so", "write", (void *)hook_write, nullptr);
xhook_register("libbase.so", "write", (void *)hook_write, nullptr);
xhook_register("libartbase.so", "write", (void *)hook_write, nullptr);
xhook_refresh(0);
xhook_clear();
}
ssize_t hook_write(int fd, const void *buf, size_t count) {
if (fd != hprofFd) {
ssize_t total_write = write(fd, buf, count);
return total_write;
}
//每次hook_write,初始化重置
reset();
const unsigned char tag = ((unsigned char *)buf)[0];
//删除掉无关record tag类型匹配,只匹配heap相关提高性能
switch (tag) {
case HPROF_TAG_HEAP_DUMP:
case HPROF_TAG_HEAP_DUMP_SEGMENT: {
processHeap(buf, HEAP_TAG_BYTE_SIZE + RECORD_TIME_BYTE_SIZE + RECORD_LENGTH_BYTE_SIZE, count,
heapSerialNum, 0);
heapSerialNum++;
} break;
default:
break;
}
//根据裁剪掉的zygote space和image space更新length
int recordLength = 0;
if (tag == HPROF_TAG_HEAP_DUMP || tag == HPROF_TAG_HEAP_DUMP_SEGMENT) {
recordLength =
getIntFromBytes((unsigned char *)buf, HEAP_TAG_BYTE_SIZE + RECORD_TIME_BYTE_SIZE);
recordLength -= stripBytesSum;
int index = HEAP_TAG_BYTE_SIZE + RECORD_TIME_BYTE_SIZE;
((unsigned char *)buf)[index] =
(unsigned char)(((unsigned int)recordLength & 0xff000000u) >> 24u);
((unsigned char *)buf)[index + 1] =
(unsigned char)(((unsigned int)recordLength & 0x00ff0000u) >> 16u);
((unsigned char *)buf)[index + 2] =
(unsigned char)(((unsigned int)recordLength & 0x0000ff00u) >> 8u);
((unsigned char *)buf)[index + 3] = (unsigned char)((unsigned int)recordLength & 0x000000ffu);
}
ssize_t total_write = 0;
int startIndex = 0;
for (int i = 0; i < stripIndex; i++) {
//将裁剪掉的区间,通过写时过滤掉, that is, only write stripIndexListPair[i * 2 + 1] —— stripIndexListPair[(i + 1) * 2] area
void *writeBuf = (void *)((unsigned char *)buf + startIndex);
auto writeLen = (size_t)(stripIndexListPair[i * 2] - startIndex);
if (writeLen > 0) {
total_write += write(fd, writeBuf, writeLen);
} else if (writeLen < 0) {
__android_log_print(ANDROID_LOG_ERROR, "HprofDump", "hook_write array i:%d writeLen<0:%lu", i,
writeLen);
}
startIndex = stripIndexListPair[i * 2 + 1];
}
//write tail bytes
auto writeLen = (size_t)(count - startIndex);
if (writeLen > 0) {
void *writeBuf = (void *)((unsigned char *)buf + startIndex);
total_write += write(fd, writeBuf, count - startIndex);
}
hookWriteSerialNum++;
if (total_write != count) {
__android_log_print(ANDROID_LOG_INFO, "HprofDump", "hook write, hprof strip happens");
}
return count;
}
int processHeap(const void *buf, int firstIndex, int maxLen, int heapSerialNo, int arraySerialNo) {
if (firstIndex >= maxLen) {
return arraySerialNo;
}
const unsigned char subtag = ((unsigned char *)buf)[firstIndex];
switch (subtag) {
case HPROF_OBJECT_ARRAY_DUMP: {
int length = getIntFromBytes((unsigned char *)buf, firstIndex + HEAP_TAG_BYTE_SIZE +
OBJECT_ID_BYTE_SIZE +
STACK_TRACE_SERIAL_NUMBER_BYTE_SIZE);
//裁剪掉system space
//stripBytesSum = 本次记录的stripIndexListPair区间(待裁剪掉的区域)长度,
//stripIndex++固定一次;
if (isCurrentSystemHeap) {
stripIndexListPair[stripIndex * 2] = firstIndex;
stripIndexListPair[stripIndex * 2 + 1] = firstIndex + HEAP_TAG_BYTE_SIZE +
OBJECT_ID_BYTE_SIZE +
STACK_TRACE_SERIAL_NUMBER_BYTE_SIZE + U4 /*Length*/
+ CLASS_ID_BYTE_SIZE + U4 /*Id*/ * length;
stripIndex++;
stripBytesSum += HEAP_TAG_BYTE_SIZE + OBJECT_ID_BYTE_SIZE +
STACK_TRACE_SERIAL_NUMBER_BYTE_SIZE + U4 /*Length*/
+ CLASS_ID_BYTE_SIZE + U4 /*Id*/ * length;
}
arraySerialNo = processHeap(buf,
firstIndex + HEAP_TAG_BYTE_SIZE + OBJECT_ID_BYTE_SIZE +
STACK_TRACE_SERIAL_NUMBER_BYTE_SIZE + U4 /*Length*/
+ CLASS_ID_BYTE_SIZE + U4 /*Id*/ * length,
maxLen, heapSerialNo, arraySerialNo);
} break;
}
}
const int STRIP_LIST_LENGTH = 65536 * 2 * 2 + 2;
int stripIndexListPair[STRIP_LIST_LENGTH];//stripIndex*2 —— stripIndex*2+1 indicate that area need to be stripped
int stripIndex = 0;
int stripBytesSum = 0;
void reset() {
stripIndex = 0;
stripBytesSum = 0;
}