Abstract
KOOM is an open source memory monitoring library developed by Kuahand. Compared to Leakcanary’s offline monitoring library, it is more efficient in the way of memory monitoring, memory leak detection and snapshot file generation, and can be used as an online monitoring option for projects.
For the general open source library, I used to organize the structure of the project, so that we can have a macro understanding of the function points and implementation scheme of the library, rather than starting from the source code like most articles on the Internet, it is difficult to grasp the key points in the process of reading.
The directory structure
As can be seen from the figure above, it is mainly divided into the following functions:
- Monitor: a memory monitoring module that monitors the memory during application running
- Dump: The memory mirror dumps the hprof file
- Analysis: Memory file analysis module, that is, analysis of hprof files
- Common: Basic function module, which contains some common base classes
- Report: Analysis report file module for uploading files
The class diagram structure
Before reading the source code, we need to summarize a few important classes. Here is the class diagram
Class action
class | role |
---|---|
KOOM | The entry class, which initializes some classes, then starts checking memory |
KOOMInternal | The implementation class of detection memory encapsulates the monitoring and analysis functions |
HeapDumpListener | Memory dump listener, implemented by KOOMInternal |
HeapAnalysisListener | Memory analysis listener, which KOOMInternal implements |
HeapDumpTrigger | Encapsulates memory monitoring, memory trigger, and dump result notification |
HeapAnalysisTrigger | Encapsulated file analysis of the entry, internal will open a child process for analysis |
MonitorManager | Monitor management classes |
Monitor | Monitor interface, currently there is only one HeapMonitor implementation class |
MonitorThread | Start a background thread for monitoring |
KTrigger | Trigger interface, there are some general interface, the current implementation classes are HeapDumpTrigger and HeapAnalysisTrigger |
I’ve listed the framework and some important classes above, and along this thread we’ll start with source code.
Source code analysis
1. Start from the KOOM entrance
private KOOM(Application application) { if (! inited) init(application); internal = new KOOMInternal(application); } public static void init(Application application) { KLog.init(new KLog.DefaultLogger()); if (inited) { KLog.i(TAG, "already init!" ); return; } inited = true; if (koom == null) { koom = new KOOM(application); } koom.start(); } public void start() { internal.start(); }Copy the code
- Generally, the init interface is called in the Application startup phase, which is called KLog initialization, which is the encapsulation of the Log function.
- If it has already been initialized, return it; otherwise, initialize KOOM
- Check KOOM’s constructor for KOOMInternal initialization
- Finally, call KOOM start. Check out the implementation and finally call KOOMInternal start
2. We follow the constructor of KOOMInternal
Public KOOMInternal(Application Application) {kutils.startup (); // Initialize the memory monitor configuration buildConfig(application); // Initialize the memory monitor and memory trigger classes heapDumpTrigger = new heapDumpTrigger (); HeapAnalysisTrigger = new heapAnalysisTrigger (); / / analysis on life cycle to monitor ProcessLifecycleOwner. The get (). GetLifecycle (). The addObserver (heapAnalysisTrigger); } private void buildConfig(Application Application) {//setApplication must be the first KGlobalConfig.setApplication(application); // Initialize global memory configuration kglobalconfig.setkConfig (kconfig.defaultConfig ()); }Copy the code
The constructor is mainly used to configure memory monitoring parameters, initialize memory monitoring, and file analysis classes.
3. Look again at the memory parameter configuration code
public static KConfig defaultConfig() { return new KConfigBuilder().build(); } public KConfigBuilder () {/ / Config the configuration of enclosing heapRatio = KConstants. HeapThreshold. GetDefaultPercentRation (); this.heapMaxRatio = KConstants.HeapThreshold.getDefaultMaxPercentRation(); this.heapOverTimes = KConstants.HeapThreshold.OVER_TIMES; this.heapPollInterval = KConstants.HeapThreshold.POLL_INTERVAL; File cacheFile = KGlobalConfig.getApplication().getCacheDir(); //issue https://github.com/KwaiAppTeam/KOOM/issues/30 this.rootDir = cacheFile ! = null ? cacheFile.getAbsolutePath() + File.separator + KOOM_DIR : "/data/data/" + KGlobalConfig.getApplication().getPackageName() + "/cache/" + KOOM_DIR; File dir = new File(rootDir); if (! dir.exists()) dir.mkdirs(); this.processName = KGlobalConfig.getApplication().getPackageName(); } public static class HeapThreshold { public static int VM_512_DEVICE = 510; public static int VM_256_DEVICE = 250; public static int VM_128_DEVICE = 128; public static float PERCENT_RATIO_IN_512_DEVICE = 80; public static float PERCENT_RATIO_IN_256_DEVICE = 85; public static float PERCENT_RATIO_IN_128_DEVICE = 90; public static float PERCENT_MAX_RATIO = 95; public static float getDefaultPercentRation() { int maxMem = (int) (Runtime.getRuntime().maxMemory() / MB); if (Debug.VERBOSE_LOG) { KLog.i("koom", "max mem " + maxMem); } if (maxMem >= VM_512_DEVICE ) { return KConstants.HeapThreshold. PERCENT_RATIO_IN_512_DEVICE ; } else if (maxMem >= VM_256_DEVICE ) { return KConstants.HeapThreshold. PERCENT_RATIO_IN_256_DEVICE ; } else if (maxMem >= VM_128_DEVICE ) { return KConstants.HeapThreshold. PERCENT_RATIO_IN_128_DEVICE ; } return KConstants.HeapThreshold.PERCENT_RATIO_IN_512_DEVICE; } public static float getDefaultMaxPercentRation() { return KConstants.HeapThreshold.PERCENT_MAX_RATIO; } public static int OVER_TIMES = 3; public static int POLL_INTERVAL = 5000; } public KConfig build() { if (heapRatio > heapMaxRatio) { throw new RuntimeException("heapMaxRatio be greater than heapRatio"); } HeapThreshold heapThreshold = new HeapThreshold(heapRatio, heapMaxRatio, heapOverTimes, heapPollInterval); return new KConfig(heapThreshold, this.rootDir, this.processName); }Copy the code
- You can see that different trigger rules are assigned according to the amount of memory allocated by different phones
There are mainly the following four kinds: B. maxMem < 510 && maxMem >= 250, maxMem < 250 && maxMem >= 128, More than 90 triggers 2. And set the maximum memory trigger is 95% 3 times out of order is 3 times
Variables are initialized first, and dump triggering rules are covered later.
4. Look at the constructor of HeapDumpTrigger
public HeapDumpTrigger() {
monitorManager = new MonitorManager();
monitorManager.addMonitor(new HeapMonitor());
heapDumper = new ForkJvmHeapDumper();
}
Copy the code
- Initializes MonitorManager and sets HeapMonitor to it, which is the implementation class for memory monitoring
- ForkJvmHeapDumper is an implementation class for Dump memory
We can probably guess from the constructor that HeapDumpTrigger monitors memory and encapsulates memory dumps.
5. After basic configuration and initialization classes are complete, return to the Start interface of KOOMInternal
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 boolean started; Private void startInternal() {try {// If (started) {klog. I (TAG, "already started!" ); return; } started = true; / / set the listener, dump the results and analysis of the results will eventually return to KOOMInteral / / heapDumpTrigger. SetHeapDumpListener (this); heapAnalysisTrigger.setHeapAnalysisListener(this); / / do some inspection work, mainly is the android version is in line with the requirements, storage control / / set an expiration time, enough is now 15 days, operation process and main process whether in the same process if (KOOMEnableChecker. DoCheck ()! = KOOMEnableChecker.Result.NORMAL) { KLog.e(TAG, "koom start failed, check result: " + KOOMEnableChecker.doCheck()); return; } ReanalysisChecker reanalysisChecker = new ReanalysisChecker(); if (reanalysisChecker.detectReanalysisFile() ! = null) { KLog.i(TAG, "detected reanalysis file"); heapAnalysisTrigger .trigger(TriggerReason.analysisReason(TriggerReason.AnalysisReason.REANALYSIS)); return; } / / trigger memory monitoring heapDumpTrigger startTrack (); } catch (Exception e) { e.printStackTrace(); }} 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; }Copy the code
We mainly did the following things:
- Start a background thread
- Return directly if the task has been started
- Set up listeners for memory monitoring and analysis
- Check whether this task can be performed
- Check that the report file has been analyzed and re-analyzed
- Enabling Memory Monitoring
6. Follow up startTrack interface and directly look at key points
@Override public void startTrack() { monitorManager.start(); / / trigger dump listening monitorManager. SetTriggerListener ((monitorType, reason) - > {the trigger (reason); return true; }); } public void start() { monitorThread.start(monitors); } public void start(List<Monitor> monitors) { stop = false; Log.i(TAG, "start"); List<Runnable> runnables = new ArrayList<>(); for (Monitor monitor : monitors) { monitor.start(); runnables.add(new MonitorRunnable(monitor)); } for (Runnable runnable : runnables) { handler.post(runnable); } } class MonitorRunnable implements Runnable { private Monitor monitor; public MonitorRunnable(Monitor monitor) { this.monitor = monitor; } @Override public void run() { if (stop) { return; } if (KConstants.Debug.VERBOSE_LOG) { Log.i(TAG, monitor.monitorType() + " monitor run"); } // Whether to trigger, The implementation class is HeapMonitor if (monitor.istrigger ()) {log. I (TAG, monitor.monitorType() + " monitor " + monitor.monitorType() + " trigger"); stop = monitorTriggerListener .onTrigger(monitor.monitorType(), monitor.getTriggerReason()); } if (! stop) { handler.postDelayed(this, monitor.pollInterval()); }}}Copy the code
Look again at HeapMonitor’s isTrigger()
@Override public boolean isTrigger() { if (! started) { return false; HeapStatus = currentHeapStatus(); // The maximum threshold has been reached, forcing trigger to prevent OOM process Crash due to large memory allocation. Unable to trigger the trigger the if (heapStatus isOverMaxThreshold) {KLog i. (TAG, "heap, informs is over Max thewire, force trigger and over times reset to 0"); currentTimes = 0; return true; } / / if this more than the threshold the if (heapStatus. IsOverThreshold) {KLog i. (TAG, "heap status used:" + heapStatus.used / KConstants.Bytes.MB + ", max:" + heapStatus.max / KConstants.Bytes.MB + ", last over times:" + currentTimes); / / is rising, and here the default is true if (heapThreshold. Ascending ()) {/ / if the last memory not overweight | | | the allocated memory is greater than the last | has reached its maximum threshold / / count + 1, Counting or reset the if (lastHeapStatus = = null | | heapStatus. 2 > = lastHeapStatus. 2 | | heapStatus. IsOverMaxThreshold) { currentTimes++; } else { KLog.i(TAG, "heap status used is not ascending, and over times reset to 0"); currentTimes = 0; } } else { currentTimes++; }} else {// if currentTimes = 0; } lastHeapStatus = heapStatus; // Dump return currentTimes >= heapthreshold.overtimes (); }Copy the code
The above logical flow of judgment is:
- If the maximum memory threshold is exceeded, dump is triggered
- If this time exceeds the specified memory setting threshold, then judge the following conditions are true, count +1; The last memory not to exceed bid | | | the allocated memory is greater than the last | has reached maximum threshold
- Otherwise the reset
- If the number of counts exceeds the threshold, dump is triggered
Conclusion down is the biggest memory more than threshold | | memory more than a given threshold and continuous growth of three consecutive will trigger the dump. This should be designed to trigger DUMP operations infrequently.
Going back to runnable, the listener is called, and if stop is true, the detection is stopped. Go back to startTrack() and finally call the trigger interface
@Override public void startTrack() { monitorManager.start(); / / trigger dump listening monitorManager. SetTriggerListener ((monitorType, reason) - > {the trigger (reason); return true; }); }Copy the code
7. Look at the trigger method
@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 {// Main logic 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) {/ / inform dump successful heapDumpListener onHeapDumped (reason); } else { KLog.e(TAG, "heap dump failed!" ); heapDumpListener.onHeapDumpFailed(); KHeapFile.delete(); }}Copy the code
Let the ForkJvmHeapDumper do the dump
public ForkJvmHeapDumper() { soLoaded = KGlobalConfig.getSoLoader().loadLib("koom-java"); if (soLoaded) { initForkDump(); } } @Override public boolean dump(String path) { KLog.i(TAG, "dump " + path); if (! soLoaded) { KLog.e(TAG, "dump failed caused by so not loaded!" ); return false; } if (! KOOMEnableChecker.get().isVersionPermit()) { KLog.e(TAG, "dump failed caused by version not permitted!" ); return false; } if (! KOOMEnableChecker.get().isSpaceEnough()) { KLog.e(TAG, "dump failed caused by disk space not enough!" ); return false; } // Compatible with Android 11 if (Build.VERSION.SDK_INT > Build.VERSION_CODES.Q) { return dumpHprofDataNative(path); } boolean dumpRes = false; TrySuspendVMThenFork (); trySuspendVMThenFork(); trySuspendVMThenFork(); If (pid == 0) {// Native dump debug. dumpHprofData(path); KLog.i(TAG, "notifyDumped:" + dumpRes); //System.exit(0); exitProcess(); } else {//native wake up all threads 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; } private boolean waitDumping(int pid) { waitPid(pid); return true; }Copy the code
This is KOOM’s efficient design, as the native dump operation will suspend all threads of the VM, then take a snapshot file, and finally restore all threads of the VM. This means that even if the background thread is enabled, it will stall, which is why Leakcanary cannot be deployed online. However, Kuaishou uses COW technology of Linux to fork sub-process for dump operation. And to solve the problem of getting stuck in the child process, the thread of the process VM in the main process is now suspended.
8. The implementation here is done through JNI.
JNIEXPORT void JNICALL Java_com_kwai_koom_javaoom_dump_ForkJvmHeapDumper_initForkDump(JNIEnv *env, jobject jObject) { if (! initForkVMSymbols()) { // Above android 11 pthread_once(&once_control, initDumpHprofSymbols); } } bool initForkVMSymbols() { void *libHandle = kwai::linker::DlFcn::dlopen("libart.so", RTLD_NOW); if (libHandle == nullptr) { return false; } suspendVM = (void (*)())kwai::linker::DlFcn::dlsym(libHandle, "_ZN3art3Dbg9SuspendVMEv"); if (suspendVM == nullptr) { __android_log_print(ANDROID_LOG_WARN, LOG_TAG, "_ZN3art3Dbg9SuspendVMEv unsupported!" ); } resumeVM = (void (*)())kwai::linker::DlFcn::dlsym(libHandle, "_ZN3art3Dbg8ResumeVMEv"); if (resumeVM == nullptr) { __android_log_print(ANDROID_LOG_WARN, LOG_TAG, "_ZN3art3Dbg8ResumeVMEv unsupported!" ); } kwai::linker::DlFcn::dlclose(libHandle); return suspendVM ! = nullptr && resumeVM ! = nullptr; } // For above android 11 static void initDumpHprofSymbols() { // Parse .dynsym(GLOBAL) void *libHandle = kwai::linker::DlFcn::dlopen("libart.so", RTLD_NOW); if (libHandle == nullptr) { return; } ScopedSuspendAllConstructor = (void (*)(void *, const char *, bool))kwai::linker::DlFcn::dlsym( libHandle, "_ZN3art16ScopedSuspendAllC1EPKcb"); if (ScopedSuspendAllConstructor == nullptr) { __android_log_print(ANDROID_LOG_WARN, LOG_TAG, "_ZN3art16ScopedSuspendAllC1EPKcb unsupported!" ); } ScopedSuspendAllDestructor = (void (*)(void *))kwai::linker::DlFcn::dlsym(libHandle, "_ZN3art16ScopedSuspendAllD1Ev"); if (ScopedSuspendAllDestructor == nullptr) { __android_log_print(ANDROID_LOG_WARN, LOG_TAG, "_ZN3art16ScopedSuspendAllD1Ev unsupported!" ); } kwai::linker::DlFcn::dlclose(libHandle); // Parse .symtab(LOCAL) libHandle = kwai::linker::DlFcn::dlopen_elf("libart.so", RTLD_NOW); if (libHandle == nullptr) { return; } HprofConstructor = (void (*)(void *, const char *, int, bool))kwai::linker::DlFcn::dlsym_elf( libHandle, "_ZN3art5hprof5HprofC2EPKcib"); if (HprofConstructor == nullptr) { __android_log_print(ANDROID_LOG_WARN, LOG_TAG, "_ZN3art5hprof5HprofC2EPKcib unsupported!" ); } HprofDestructor = (void (*)(void *))kwai::linker::DlFcn::dlsym_elf(libHandle, "_ZN3art5hprof5HprofD0Ev"); if (HprofDestructor == nullptr) { __android_log_print(ANDROID_LOG_WARN, LOG_TAG, "_ZN3art5hprof5HprofD0Ev unsupported!" ); } Dump = (void (*)(void *))kwai::linker::DlFcn::dlsym_elf(libHandle, "_ZN3art5hprof5Hprof4DumpEv"); if (Dump == nullptr) { __android_log_print(ANDROID_LOG_WARN, LOG_TAG, "_ZN3art5hprof5Hprof4DumpEv unsupported!" ); } kwai::linker::DlFcn::dlclose_elf(libHandle); }Copy the code
SuspendVM, fork, suspendResum, etc.
Different Versions of Android place different restrictions on obtaining the libart.so address.
- You can obtain the libart.so library address directly for Android N or later.
- Andoird N has added a restriction that checks the address of the calling method. If it is called by a third party, the check fails. Therefore, it is passed through the dlerror system function address to ensure that the check passes.
- Android Q version introduces the runtime namespace, so the return address is nullptr, but through the query process has been loaded dynamic library, filter out libart. So the address.
9. After the Dump is complete, the next step is to analyze the KOOMInternal callback
@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");
}
}
Copy the code
Look at the startTrack interface
@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) { //do trigger when foreground if (! isForeground) { KLog.i(TAG, "reTrigger when foreground"); this.reTriggerReason = triggerReason; return; } KLog.i(TAG, "trigger reason:" + triggerReason.analysisReason); if (triggered) { KLog.i(TAG, "Only once trigger!" ); return; } triggered = true; HeapAnalyzeReporter.addAnalysisReason(triggerReason.analysisReason); if (triggerReason.analysisReason == TriggerReason.AnalysisReason.REANALYSIS) { HeapAnalyzeReporter.recordReanalysis(); } //test reanalysis //if (triggerReason.analysisReason ! = TriggerReason.AnalysisReason.REANALYSIS) return; if (heapAnalysisListener ! = null) { heapAnalysisListener.onHeapAnalysisTrigger(); } try {// Identify a service to analyze, and in the child process doAnalysis(kGlobalConfig.getApplication ()); } catch (Exception e) { KLog.e(TAG, "doAnalysis failed"); e.printStackTrace(); if (heapAnalysisListener ! = null) { heapAnalysisListener.onHeapAnalyzeFailed(); } } } @OnLifecycleEvent(Lifecycle.Event.ON_STOP) public void onBackground() { KLog.i(TAG, "onBackground"); isForeground = false; } @OnLifecycleEvent(Lifecycle.Event.ON_START) public void onForeground() { KLog.i(TAG, "onForeground"); isForeground = true; if (reTriggerReason ! = null) { TriggerReason tmpReason = reTriggerReason; reTriggerReason = null; trigger(tmpReason); }}Copy the code
- Here you can see that Lifecycle is used and analysis is done only if the application is in the foreground.
- The important thing to note here is that since the analysis process is performance-consuming, the analysis process is performed in the child process so as not to affect the main process.
application>
<service
android:name=".analysis.HeapAnalyzeService"
android:process=":heap_analysis" />
</application>
Copy the code
10. Next, look at the implementation of HeapAnalyzeService
public class HeapAnalyzeService extends IntentService { 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(); }}); } private ResultReceiver ipcReceiver; private KHeapAnalyzer heapAnalyzer; @Override protected void onHandleIntent(Intent intent) { KLog.i(TAG, "start analyze pid:" + android.os.Process.myPid()); boolean res = false; try { beforeAnalyze(intent); res = doAnalyze(); } catch (Throwable e) { e.printStackTrace(); } if (ipcReceiver ! = null) { ipcReceiver.send(res ? IPCReceiver.RESULT_CODE_OK : IPCReceiver.RESULT_CODE_FAIL, null); } } /** * run in the heap_analysis process * * @param intent intent contains device running meta info from main process */ private void beforeAnalyze(Intent intent) { assert intent ! = null; ipcReceiver = intent.getParcelableExtra(KConstants.ServiceIntent.RECEIVER); KHeapFile heapFile = intent.getParcelableExtra(KConstants.ServiceIntent.HEAP_FILE); KHeapFile.buildInstance(heapFile); assert heapFile ! = null; heapAnalyzer = new KHeapAnalyzer(heapFile); } /** * run in the heap_analysis process */ private boolean doAnalyze() { return heapAnalyzer.analyze(); }Copy the code
It is an IntentService and is started in a child process, the same implementation as Leakcanary, see onHandleIntent
private void beforeAnalyze(Intent intent) { assert intent ! = null; ipcReceiver = intent.getParcelableExtra(KConstants.ServiceIntent.RECEIVER); KHeapFile heapFile = intent.getParcelableExtra(KConstants.ServiceIntent.HEAP_FILE); KHeapFile.buildInstance(heapFile); assert heapFile ! = null; heapAnalyzer = new KHeapAnalyzer(heapFile); }Copy the code
Before analysis, obtain the analysis file and the upload file to be written and encapsulate it into KHeapFile, which is a Parceable that encapsulates the dump Hprof address and the address of the file to be uploaded.
Look again at the Analyze () interface of KHeapAnalyzer
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;
}
Copy the code
Hprof file analysis using leakcanary2.x after the Shark library, and has been optimized. I won’t go into details here.
11. Output the Report file
Finally, analysis results are generated in a Report file in HeapAnalyzeReporter
private void flushFile() { FileOutputStream fos = null; try { String str = gson.toJson(heapReport); fos = new FileOutputStream(reportFile); KLog.i(TAG, "flushFile " + reportFile.getPath() + " str:" + str); fos.write(str.getBytes()); } catch (IOException e) { e.printStackTrace(); } finally { KUtils.closeQuietly(fos); }}Copy the code
conclusion
Compared to Leakcanary, KOOM has a number of optimizations that allow it to be used as an online monitoring tool.
- Leakcanary leverages the Activity lifecycle + weak reference mechanism +GC, which is completely accurate due to GC and may have a performance drain. The KOOM uses memory threshold detection, so there is no performance drain.
- Leakcanary Dumps the mirror in the main process, which is why it cannot be used as an online tool, while KOOM forks the dump and there is no lag.
Of course, KOOM is also a variety of optimizations based on Leakcanary. What tools to choose will be determined according to the needs of your own project.