内容简介:年前,微信开源了Resource Canary主要是用来检测Activit级别的内存泄漏、以及重复创建的冗余Bitmap。整体代码分为两部分:客户端检测内存泄漏、裁剪Hprof文件,服务端分析回传的Hprof文件。这部分代码位于
年前,微信开源了 Matrix 项目,提供了Android、ios的APM实现方案。对于Android端实现,主要包括 APK Checker
、 Resource Canary
、 Trace Canary
、 SQLite Lint
、 IO Canary
五部分。本文主要介绍 Resource Canary
的源码实现,其他部分的源码分析将在后续推出。
代码框架分析
Resource Canary主要是用来检测Activit级别的内存泄漏、以及重复创建的冗余Bitmap。整体代码分为两部分:客户端检测内存泄漏、裁剪Hprof文件,服务端分析回传的Hprof文件。
客户端监控内存泄漏、裁剪Hprof文件
这部分代码位于 matrix-resource-canary-android
模块下。监控Activity泄漏的大致流程如下:
- 通过Application的ActivityLifecycleCallbacks回调,获取已经destory的Activity信息;
- 后台线程每一分钟检测一次是否存在内存泄漏;
- 若发现内存泄漏,dump内存信息,并裁剪Hprof文件上报;
获取可能存在泄漏的Activity信息
private final Application.ActivityLifecycleCallbacks mRemovedActivityMonitor = new ActivityLifeCycleCallbacksAdapter() { @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) { mCurrentCreatedActivityCount.incrementAndGet(); } @Override public void onActivityDestroyed(Activity activity) { //记录已被destory的Activity pushDestroyedActivityInfo(activity); } }; 复制代码
private void pushDestroyedActivityInfo(Activity activity) { final String activityName = activity.getClass().getName(); //该Activity确认存在泄漏,且已经上报 if (isPublished(activityName)) { MatrixLog.d(TAG, "activity leak with name %s had published, just ignore", activityName); return; } final UUID uuid = UUID.randomUUID(); final StringBuilder keyBuilder = new StringBuilder(); //生成Activity实例的唯一标识 keyBuilder.append(ACTIVITY_REFKEY_PREFIX).append(activityName) .append('_').append(Long.toHexString(uuid.getMostSignificantBits())).append(Long.toHexString(uuid.getLeastSignificantBits())); final String key = keyBuilder.toString(); //构造一个数据结构,表示一个已被destroy的Activity final DestroyedActivityInfo destroyedActivityInfo = new DestroyedActivityInfo(key, activity, activityName, mCurrentCreatedActivityCount.get()); //放入后续待检测的Activity list mDestroyedActivityInfos.add(destroyedActivityInfo); } 复制代码
检测是否存在内存泄漏
private final RetryableTask mScanDestroyedActivitiesTask = new RetryableTask() { @Override public Status execute() { // Fake leaks will be generated when debugger is attached. //Debug调试模式,检测可能失效,直接return if (Debug.isDebuggerConnected() && !mResourcePlugin.getConfig().getDetectDebugger()) { MatrixLog.w(TAG, "debugger is connected, to avoid fake result, detection was delayed."); return Status.RETRY; } //没有已被destory的Activity实例 if (mDestroyedActivityInfos.isEmpty()) { return Status.RETRY; } //创建一个对象的弱引用 final WeakReference<Object> sentinelRef = new WeakReference<>(new Object()); //尝试触发GC triggerGc(); //系统未执行GC,直接return if (sentinelRef.get() != null) { // System ignored our gc request, we will retry later. MatrixLog.d(TAG, "system ignore our gc request, wait for next detection."); return Status.RETRY; } final Iterator<DestroyedActivityInfo> infoIt = mDestroyedActivityInfos.iterator(); while (infoIt.hasNext()) { final DestroyedActivityInfo destroyedActivityInfo = infoIt.next(); //该实例对应的Activity已被标泄漏,跳过该实例 if (isPublished(destroyedActivityInfo.mActivityName)) { MatrixLog.v(TAG, "activity with key [%s] was already published.", destroyedActivityInfo.mActivityName); infoIt.remove(); continue; } //若不能通过弱引用获取到Activity实例,表示已被回收,跳过该实例 if (destroyedActivityInfo.mActivityRef.get() == null) { // The activity was recycled by a gc triggered outside. MatrixLog.v(TAG, "activity with key [%s] was already recycled.", destroyedActivityInfo.mKey); infoIt.remove(); continue; } //该Activity实例 检测到泄漏的次数+1 ++destroyedActivityInfo.mDetectedCount; //当前显示的Activity实例与泄漏的Activity实例相差几个Activity跳转 long createdActivityCountFromDestroy = mCurrentCreatedActivityCount.get() - destroyedActivityInfo.mLastCreatedActivityCount; //若改Activity实例 检测到泄漏的次数未达到阈值,或者泄漏的Activity与当前显示的Activity很靠近,可认为是一种容错手段(实际应用中有这种场景),跳过该实例 if (destroyedActivityInfo.mDetectedCount < mMaxRedetectTimes || (createdActivityCountFromDestroy < CREATED_ACTIVITY_COUNT_THRESHOLD && !mResourcePlugin.getConfig().getDetectDebugger())) { // Although the sentinel tell us the activity should have been recycled, // system may still ignore it, so try again until we reach max retry times. MatrixLog.i(TAG, "activity with key [%s] should be recycled but actually still \n" + "exists in %s times detection with %s created activities during destroy, wait for next detection to confirm.", destroyedActivityInfo.mKey, destroyedActivityInfo.mDetectedCount, createdActivityCountFromDestroy); continue; } MatrixLog.i(TAG, "activity with key [%s] was suspected to be a leaked instance.", destroyedActivityInfo.mKey); //若允许dump内存信息 if (mHeapDumper != null) { final File hprofFile = mHeapDumper.dumpHeap(); if (hprofFile != null) { markPublished(destroyedActivityInfo.mActivityName); final HeapDump heapDump = new HeapDump(hprofFile, destroyedActivityInfo.mKey, destroyedActivityInfo.mActivityName); //处理dump出的内存信息(裁剪) mHeapDumpHandler.process(heapDump); infoIt.remove(); } else { //内存dump失败 MatrixLog.i(TAG, "heap dump for further analyzing activity with key [%s] was failed, just ignore.", destroyedActivityInfo.mKey); infoIt.remove(); } } else { // Lightweight mode, just report leaked activity name. //不允许dump内存的情况下,直接上报泄漏的Activity类名 MatrixLog.i(TAG, "lightweight mode, just report leaked activity name."); markPublished(destroyedActivityInfo.mActivityName); if (mResourcePlugin != null) { final JSONObject resultJson = new JSONObject(); try { resultJson.put(SharePluginInfo.ISSUE_ACTIVITY_NAME, destroyedActivityInfo.mActivityName); } catch (JSONException e) { MatrixLog.printErrStackTrace(TAG, e, "unexpected exception."); } mResourcePlugin.onDetectIssue(new Issue(resultJson)); } } } return Status.RETRY; } }; 复制代码
裁剪Hprof文件上报
protected AndroidHeapDumper.HeapDumpHandler createHeapDumpHandler(final Context context, ResourceConfig resourceConfig) { return new AndroidHeapDumper.HeapDumpHandler() { @Override public void process(HeapDump result) { //process流程最终调用CanaryWorkerService进行裁剪和上报 CanaryWorkerService.shrinkHprofAndReport(context, result); } }; } 复制代码
public static void shrinkHprofAndReport(Context context, HeapDump heapDump) { final Intent intent = new Intent(context, CanaryWorkerService.class); intent.setAction(ACTION_SHRINK_HPROF); intent.putExtra(EXTRA_PARAM_HEAPDUMP, heapDump); enqueueWork(context, CanaryWorkerService.class, JOB_ID, intent); } 复制代码
<application> <service android:name=".CanaryWorkerService" android:process=":res_can_worker" android:permission="android.permission.BIND_JOB_SERVICE" android:exported="false"> </service> <service android:name=".CanaryResultService" android:permission="android.permission.BIND_JOB_SERVICE" android:exported="false"> </service> </application> 复制代码
CanaryWorkerService
、 CanaryResultService
都是在独立进程运行的。其中 CanaryWorkerService
主要执行 doShrinkHprofAndReport
方法:
private void doShrinkHprofAndReport(HeapDump heapDump) { final File hprofDir = heapDump.getHprofFile().getParentFile(); //裁剪之后的Hprof文件名 final File shrinkedHProfFile = new File(hprofDir, getShrinkHprofName(heapDump.getHprofFile())); final File zipResFile = new File(hprofDir, getResultZipName("dump_result_" + android.os.Process.myPid())); final File hprofFile = heapDump.getHprofFile(); ZipOutputStream zos = null; try { long startTime = System.currentTimeMillis(); //执行Hprof裁剪 new HprofBufferShrinker().shrink(hprofFile, shrinkedHProfFile); MatrixLog.i(TAG, "shrink hprof file %s, size: %dk to %s, size: %dk, use time:%d", hprofFile.getPath(), hprofFile.length() / 1024, shrinkedHProfFile.getPath(), shrinkedHProfFile.length() / 1024, (System.currentTimeMillis() - startTime)); //打成压缩包 zos = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(zipResFile))); //记录一些设备信息 final ZipEntry resultInfoEntry = new ZipEntry("result.info"); //裁剪后的Hprof文件 final ZipEntry shrinkedHProfEntry = new ZipEntry(shrinkedHProfFile.getName()); zos.putNextEntry(resultInfoEntry); final PrintWriter pw = new PrintWriter(new OutputStreamWriter(zos, Charset.forName("UTF-8"))); pw.println("# Resource Canary Result Infomation. THIS FILE IS IMPORTANT FOR THE ANALYZER !!"); //系统版本 pw.println("sdkVersion=" + Build.VERSION.SDK_INT); //厂商信息 pw.println("manufacturer=" + Build.MANUFACTURER); //裁剪后Hprof文件名 pw.println("hprofEntry=" + shrinkedHProfEntry.getName()); //泄漏Activity实例的key pw.println("leakedActivityKey=" + heapDump.getReferenceKey()); pw.flush(); zos.closeEntry(); zos.putNextEntry(shrinkedHProfEntry); copyFileToStream(shrinkedHProfFile, zos); zos.closeEntry(); //原始数据删除 shrinkedHProfFile.delete(); hprofFile.delete(); MatrixLog.i(TAG, "process hprof file use total time:%d", (System.currentTimeMillis() - startTime)); //CanaryResultService执行上报逻辑 CanaryResultService.reportHprofResult(this, zipResFile.getAbsolutePath(), heapDump.getActivityName()); } catch (IOException e) { MatrixLog.printErrStackTrace(TAG, e, ""); } finally { closeQuietly(zos); } } 复制代码
裁剪的核心代码如下:
public void shrink(File hprofIn, File hprofOut) throws IOException { FileInputStream is = null; OutputStream os = null; try { is = new FileInputStream(hprofIn); os = new BufferedOutputStream(new FileOutputStream(hprofOut)); final HprofReader reader = new HprofReader(new BufferedInputStream(is)); //1、收集Bitmap和String信息 reader.accept(new HprofInfoCollectVisitor()); // Reset. is.getChannel().position(0); //2、找到Bitmap、String中持有的byte数组,并找到内容重复的Bitmap reader.accept(new HprofKeptBufferCollectVisitor()); // Reset. is.getChannel().position(0); //3、裁剪掉内容重复的Bitmap,和其他byte数组 reader.accept(new HprofBufferShrinkVisitor(new HprofWriter(os))); } finally { if (os != null) { try { os.close(); } catch (Throwable thr) { // Ignored. } } if (is != null) { try { is.close(); } catch (Throwable thr) { // Ignored. } } } } 复制代码
- HprofInfoCollectVisitor
private class HprofInfoCollectVisitor extends HprofVisitor { HprofInfoCollectVisitor() { super(null); } @Override public void visitHeader(String text, int idSize, long timestamp) { mIdSize = idSize; mNullBufferId = ID.createNullID(idSize); } @Override public void visitStringRecord(ID id, String text, int timestamp, long length) { if (mBitmapClassNameStringId == null && "android.graphics.Bitmap".equals(text)) { //Bitmap类型String字符串的索引 mBitmapClassNameStringId = id; } else if (mMBufferFieldNameStringId == null && "mBuffer".equals(text)) { //mBuffer字段String字符串的索引 mMBufferFieldNameStringId = id; } else if (mMRecycledFieldNameStringId == null && "mRecycled".equals(text)) { //mRecycled字段String字符串的索引 mMRecycledFieldNameStringId = id; } else if (mStringClassNameStringId == null && "java.lang.String".equals(text)) { //String类型 字符串的索引 mStringClassNameStringId = id; } else if (mValueFieldNameStringId == null && "value".equals(text)) { //value字段字符串的索引 mValueFieldNameStringId = id; } } @Override public void visitLoadClassRecord(int serialNumber, ID classObjectId, int stackTraceSerial, ID classNameStringId, int timestamp, long length) { if (mBmpClassId == null && mBitmapClassNameStringId != null && mBitmapClassNameStringId.equals(classNameStringId)) { //找到Bitmap这个类的索引 mBmpClassId = classObjectId; } else if (mStringClassId == null && mStringClassNameStringId != null && mStringClassNameStringId.equals(classNameStringId)) { //找到String这个类的索引 mStringClassId = classObjectId; } } @Override public HprofHeapDumpVisitor visitHeapDumpRecord(int tag, int timestamp, long length) { return new HprofHeapDumpVisitor(null) { @Override public void visitHeapDumpClass(ID id, int stackSerialNumber, ID superClassId, ID classLoaderId, int instanceSize, Field[] staticFields, Field[] instanceFields) { if (mBmpClassInstanceFields == null && mBmpClassId != null && mBmpClassId.equals(id)) { /找到Bitmap所有实例的字段信息 mBmpClassInstanceFields = instanceFields; } else if (mStringClassInstanceFields == null && mStringClassId != null && mStringClassId.equals(id)) { //找到String所有势力的字段信息 mStringClassInstanceFields = instanceFields; } } }; } } 复制代码
这里对Bitmap、String两种类型做了处理(因为后续步骤中要采集掉byte数组)。
Bitmap在android sdk < 26之前,存储像素的byte数组是放在 Java 层的,26之后是放在native层的。
String在android sdk < 23之前,存储字符的byte数组是放在Java层的,23之后是放在native层的。
- HprofKeptBufferCollectVisitor
private class HprofKeptBufferCollectVisitor extends HprofVisitor { HprofKeptBufferCollectVisitor() { super(null); } @Override public HprofHeapDumpVisitor visitHeapDumpRecord(int tag, int timestamp, long length) { return new HprofHeapDumpVisitor(null) { @Override public void visitHeapDumpInstance(ID id, int stackId, ID typeId, byte[] instanceData) { try { //找到Bitmap实例 if (mBmpClassId != null && mBmpClassId.equals(typeId)) { ID bufferId = null; Boolean isRecycled = null; final ByteArrayInputStream bais = new ByteArrayInputStream(instanceData); for (Field field : mBmpClassInstanceFields) { final ID fieldNameStringId = field.nameId; final Type fieldType = Type.getType(field.typeId); if (fieldType == null) { throw new IllegalStateException("visit bmp instance failed, lost type def of typeId: " + field.typeId); } if (mMBufferFieldNameStringId.equals(fieldNameStringId)) { //找到这个实例mBuffer字段的索引id bufferId = (ID) IOUtil.readValue(bais, fieldType, mIdSize); } else if (mMRecycledFieldNameStringId.equals(fieldNameStringId)) { //找到这个实例mRecycled的boolean值(基础数据类型,没有引用关系) isRecycled = (Boolean) IOUtil.readValue(bais, fieldType, mIdSize); } else if (bufferId == null || isRecycled == null) { IOUtil.skipValue(bais, fieldType, mIdSize); } else { break; } } bais.close(); //确认Bitmap没有被回收 final boolean reguardAsNotRecycledBmp = (isRecycled == null || !isRecycled); if (bufferId != null && reguardAsNotRecycledBmp && !bufferId.equals(mNullBufferId)) { //将mBuffer对应的byte数组索引id加入集合 mBmpBufferIds.add(bufferId); } //如果是String类型 } else if (mStringClassId != null && mStringClassId.equals(typeId)) { ID strValueId = null; final ByteArrayInputStream bais = new ByteArrayInputStream(instanceData); for (Field field : mStringClassInstanceFields) { final ID fieldNameStringId = field.nameId; final Type fieldType = Type.getType(field.typeId); if (fieldType == null) { throw new IllegalStateException("visit string instance failed, lost type def of typeId: " + field.typeId); } if (mValueFieldNameStringId.equals(fieldNameStringId)) { //找到这个String实例的value字段对应的byte数组的索引id strValueId = (ID) IOUtil.readValue(bais, fieldType, mIdSize); } else if (strValueId == null) { IOUtil.skipValue(bais, fieldType, mIdSize); } else { break; } } bais.close(); if (strValueId != null && !strValueId.equals(mNullBufferId)) { //将value字段对应的byte数组索引id加入集合 mStringValueIds.add(strValueId); } } } catch (Throwable thr) { throw new RuntimeException(thr); } } @Override public void visitHeapDumpPrimitiveArray(int tag, ID id, int stackId, int numElements, int typeId, byte[] elements) { //将所有byte数组的索引id,以及对应byte[]数据加入集合 mBufferIdToElementDataMap.put(id, elements); } }; } @Override public void visitEnd() { final Set<Map.Entry<ID, byte[]>> idDataSet = mBufferIdToElementDataMap.entrySet(); final Map<String, ID> duplicateBufferFilterMap = new HashMap<>(); for (Map.Entry<ID, byte[]> idDataPair : idDataSet) { final ID bufferId = idDataPair.getKey(); final byte[] elementData = idDataPair.getValue(); //如果这块byte数组不属于Bitmap,continue if (!mBmpBufferIds.contains(bufferId)) { // Discard non-bitmap buffer. continue; } 计算byte[]数据的md5 final String buffMd5 = DigestUtil.getMD5String(elementData); final ID mergedBufferId = duplicateBufferFilterMap.get(buffMd5); //若内存中Bitmap不存在重复的byte[]数据 if (mergedBufferId == null) { duplicateBufferFilterMap.put(buffMd5, bufferId); } else { //若Bitmap存在重复的byte[]数据,所有引用都指向同一块byte数组的索引(方便后续裁剪掉重复的byte[]数据) mBmpBufferIdToDeduplicatedIdMap.put(mergedBufferId, mergedBufferId); mBmpBufferIdToDeduplicatedIdMap.put(bufferId, mergedBufferId); } } // Save memory cost. mBufferIdToElementDataMap.clear(); } } 复制代码
- HprofBufferShrinkVisitor
private class HprofBufferShrinkVisitor extends HprofVisitor { HprofBufferShrinkVisitor(HprofWriter hprofWriter) { super(hprofWriter); } @Override public HprofHeapDumpVisitor visitHeapDumpRecord(int tag, int timestamp, long length) { return new HprofHeapDumpVisitor(super.visitHeapDumpRecord(tag, timestamp, length)) { @Override public void visitHeapDumpInstance(ID id, int stackId, ID typeId, byte[] instanceData) { try { //如果是Bitmap类型 if (typeId.equals(mBmpClassId)) { ID bufferId = null; int bufferIdPos = 0; final ByteArrayInputStream bais = new ByteArrayInputStream(instanceData); for (Field field : mBmpClassInstanceFields) { final ID fieldNameStringId = field.nameId; final Type fieldType = Type.getType(field.typeId); if (fieldType == null) { throw new IllegalStateException("visit instance failed, lost type def of typeId: " + field.typeId); } if (mMBufferFieldNameStringId.equals(fieldNameStringId)) { bufferId = (ID) IOUtil.readValue(bais, fieldType, mIdSize); break; } else { bufferIdPos += IOUtil.skipValue(bais, fieldType, mIdSize); } } //如果该实例的mBuffer字段的索引不为null if (bufferId != null) { //获取去重后的byte数组索引(若有内容重复的byte[]数据,最后都会指向一个id索引) final ID deduplicatedId = mBmpBufferIdToDeduplicatedIdMap.get(bufferId); if (deduplicatedId != null && !bufferId.equals(deduplicatedId) && !bufferId.equals(mNullBufferId)) { //更新byte数组的索引id modifyIdInBuffer(instanceData, bufferIdPos, deduplicatedId); } } } } catch (Throwable thr) { throw new RuntimeException(thr); } super.visitHeapDumpInstance(id, stackId, typeId, instanceData); } private void modifyIdInBuffer(byte[] buf, int off, ID newId) { final ByteBuffer bBuf = ByteBuffer.wrap(buf); bBuf.position(off); bBuf.put(newId.getBytes()); } @Override public void visitHeapDumpPrimitiveArray(int tag, ID id, int stackId, int numElements, int typeId, byte[] elements) { //重复的byte数组索引 重定向之后的 索引id final ID deduplicatedID = mBmpBufferIdToDeduplicatedIdMap.get(id); // Discard non-bitmap or duplicated bitmap buffer but keep reference key. if (deduplicatedID == null || !id.equals(deduplicatedID)) { //不记录重复的byte[]数据,直接return if (!mStringValueIds.contains(id)) { return; } } super.visitHeapDumpPrimitiveArray(tag, id, stackId, numElements, typeId, elements); } }; } } 复制代码
Hprof文件裁剪的过程主要是裁剪了重复Bitmap的byte[]数据,裁剪的力度不是很大。(是不是可以只保留引用链,丢弃所有的PrimitiveArray?这里保留Bitmap的原因是回传之后,可以还原出png图片信息;感觉Bitmap用处不是很多,还狠很多裁剪的空间)。
最后是裁剪后的Hprof文件的上报,在 CanaryResultService
这个Service中
@Override protected void onHandleWork(Intent intent) { if (intent != null) { final String action = intent.getAction(); if (ACTION_REPORT_HPROF_RESULT.equals(action)) { final String resultPath = intent.getStringExtra(EXTRA_PARAM_RESULT_PATH); final String activityName = intent.getStringExtra(EXTRA_PARAM_ACTIVITY); if (resultPath != null && !resultPath.isEmpty() && activityName != null && !activityName.isEmpty()) { doReportHprofResult(resultPath, activityName); } else { MatrixLog.e(TAG, "resultPath or activityName is null or empty, skip reporting."); } } } } private void doReportHprofResult(String resultPath, String activityName) { try { final JSONObject resultJson = new JSONObject(); // resultJson = DeviceUtil.getDeviceInfo(resultJson, getApplication()); resultJson.put(SharePluginInfo.ISSUE_RESULT_PATH, resultPath); resultJson.put(SharePluginInfo.ISSUE_ACTIVITY_NAME, activityName); Plugin plugin = Matrix.with().getPluginByClass(ResourcePlugin.class); if (plugin != null) { plugin.onDetectIssue(new Issue(resultJson)); } } catch (Throwable thr) { MatrixLog.printErrStackTrace(TAG, thr, "unexpected exception, skip reporting."); } } 复制代码
服务端分析裁剪后的Hprof文件
Java内存回收的原理是判断该对象是否有到GCRoot的引用链。此处分析Hprof的原则,也是获取泄漏的Activity到GCRoot的引用链。
首先,明确一下哪些对象属于GCRoot;
GCRoot类型 | 说明 |
---|---|
Stack Local | Java方法的local变量或参数 |
Held by JVM | 用于JVM特殊目的由GC保留的对象,但实际上这个与JVM的实现是有关的。可能已知的一些类型是:系统类加载器、一些JVM知道的重要的异常类、一些用于处理异常的预分配对象以及一些自定义的类加载器等 |
JNI Local | JNI方法的local变量或参数 |
JNI Global | 全局JNI引用 |
Thread | 活着的线程 |
Monitor Used | 用于同步的监控对象 |
Java static field | Java类的静态属性 |
常见的就是这几种,完整说明可以查看dalvik中的定义
在Resource Canary的代码中,通过以下这些GCRoot类型来查找引用链
private void enqueueGcRoots(Snapshot snapshot) { for (RootObj rootObj : snapshot.getGCRoots()) { switch (rootObj.getRootType()) { //Java栈帧中的局部变量 case JAVA_LOCAL: Instance thread = HahaSpy.allocatingThread(rootObj); String threadName = threadName(thread); Exclusion params = excludedRefs.threadNames.get(threadName); if (params == null || !params.alwaysExclude) { enqueue(params, null, rootObj, null, null); } break; case INTERNED_STRING: case DEBUGGER: case INVALID_TYPE: // An object that is unreachable from any other root, but not a root itself. case UNREACHABLE: case UNKNOWN: // An object that is in a queue, waiting for a finalizer to run. case FINALIZING: break; //系统确认的一些GCRoot case SYSTEM_CLASS: //JNI的局部变量 case VM_INTERNAL: // A local variable in native code. //JNI的全局变量 case NATIVE_LOCAL: // A global variable in native code. //active线程持有的 case NATIVE_STATIC: // An object that was referenced from an active thread block. //用于同步锁的监控对象 case THREAD_BLOCK: // Everything that called the wait() or notify() methods, or that is synchronized. case BUSY_MONITOR: case NATIVE_MONITOR: case REFERENCE_CLEANUP: // Input or output parameters in native code. case NATIVE_STACK: //Java类的静态变量 case JAVA_STATIC: enqueue(null, null, rootObj, null, null); break; default: throw new UnsupportedOperationException("Unknown root type:" + rootObj.getRootType()); } } } 复制代码
下面来看下分析的入口方法
private static void analyzeAndStoreResult(File hprofFile, int sdkVersion, String manufacturer, String leakedActivityKey, JSONObject extraInfo) throws IOException { final HeapSnapshot heapSnapshot = new HeapSnapshot(hprofFile); //系统问题可能导致的一些泄漏,可以认为排除掉 final ExcludedRefs excludedRefs = AndroidExcludedRefs.createAppDefaults(sdkVersion, manufacturer).build(); //获取到Activity泄漏的结果 final ActivityLeakResult activityLeakResult = new ActivityLeakAnalyzer(leakedActivityKey, excludedRefs).analyze(heapSnapshot); DuplicatedBitmapResult duplicatedBmpResult = DuplicatedBitmapResult.noDuplicatedBitmap(0); //Android sdk 26以下获取重复Bitmap的结果 if (sdkVersion < 26) { final ExcludedBmps excludedBmps = AndroidExcludedBmpRefs.createDefaults().build(); duplicatedBmpResult = new DuplicatedBitmapAnalyzer(mMinBmpLeakSize, excludedBmps).analyze(heapSnapshot); } else { System.err.println("\n ! SDK version of target device is larger or equal to 26, " + "which is not supported by DuplicatedBitmapAnalyzer."); } ... } 复制代码
ActivityLeakAnalyzer
这个类就是分析从GCRoot到泄漏Activity实例的引用链。
private ActivityLeakResult checkForLeak(HeapSnapshot heapSnapshot, String refKey) { long analysisStartNanoTime = System.nanoTime(); try { final Snapshot snapshot = heapSnapshot.getSnapshot(); //找到泄漏的Activity实例 final Instance leakingRef = findLeakingReference(refKey, snapshot); // False alarm, weak reference was cleared in between key check and heap dump. //若找不到,说明已被回收 if (leakingRef == null) { return ActivityLeakResult.noLeak(AnalyzeUtil.since(analysisStartNanoTime)); } //寻找GCRoot到泄漏Activity的引用链 return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef); } catch (Throwable e) { e.printStackTrace(); return ActivityLeakResult.failure(e, AnalyzeUtil.since(analysisStartNanoTime)); } } 复制代码
寻找泄漏Activity实例,是通过检测Activity泄漏时使用到的 DestroyedActivityInfo
类来判断的。
public class DestroyedActivityInfo { //通过判断内存dump文件Hprof中实例的key与传入的key是否一致,判断是泄漏的Activity实例 public final String mKey; public final String mActivityName; //通过弱引用获取到这个实例 public final WeakReference<Activity> mActivityRef; public final long mLastCreatedActivityCount; public int mDetectedCount = 0; public DestroyedActivityInfo(String key, Activity activity, String activityName, long lastCreatedActivityCount) { mKey = key; mActivityName = activityName; mActivityRef = new WeakReference<>(activity); mLastCreatedActivityCount = lastCreatedActivityCount; } } 复制代码
private Instance findLeakingReference(String key, Snapshot snapshot) { // private static final String DESTROYED_ACTIVITY_INFO_CLASSNAME= "com.tencent.matrix.resource.analyzer.model.DestroyedActivityInfo"; final ClassObj infoClass = snapshot.findClass(DESTROYED_ACTIVITY_INFO_CLASSNAME); if (infoClass == null) { throw new IllegalStateException("Unabled to find destroy activity info class with name: " + DESTROYED_ACTIVITY_INFO_CLASSNAME); } List<String> keysFound = new ArrayList<>(); //遍历DestroyedActivityInfo的所有实例 for (Instance infoInstance : infoClass.getInstancesList()) { final List<ClassInstance.FieldValue> values = classInstanceValues(infoInstance); // private static final String ACTIVITY_REFERENCE_KEY_FIELDNAME = "mKey"; final String keyCandidate = asString(fieldValue(values, ACTIVITY_REFERENCE_KEY_FIELDNAME)); if (keyCandidate.equals(key)) { // private static final String ACTIVITY_REFERENCE_FIELDNAME = "mActivityRef"; final Instance weakRefObj = fieldValue(values, ACTIVITY_REFERENCE_FIELDNAME); if (weakRefObj == null) { continue; } final List<ClassInstance.FieldValue> activityRefs = classInstanceValues(weakRefObj); //获取弱引用中的真正对象实例 return fieldValue(activityRefs, "referent"); } keysFound.add(keyCandidate); } throw new IllegalStateException( "Could not find weak reference with key " + key + " in " + keysFound); } 复制代码
获取到泄漏的Activity实例之后,就需要找到GCToot到该实例的引用链。
private ActivityLeakResult findLeakTrace(long analysisStartNanoTime, Snapshot snapshot, Instance leakingRef) { //路径搜索帮助类,可以设置一些不用考虑的规则(不用搜索相关分叉) ShortestPathFinder pathFinder = new ShortestPathFinder(mExcludedRefs); //找到最短引用链,并返回结果 ShortestPathFinder.Result result = pathFinder.findPath(snapshot, leakingRef); // False alarm, no strong reference path to GC Roots. //无引用链 if (result.referenceChainHead == null) { return ActivityLeakResult.noLeak(AnalyzeUtil.since(analysisStartNanoTime)); } final ReferenceChain referenceChain = result.buildReferenceChain(); final String className = leakingRef.getClassObj().getClassName(); //若是命中exclude规则,返回无引用链 if (result.excludingKnown || referenceChain.isEmpty()) { return ActivityLeakResult.noLeak(AnalyzeUtil.since(analysisStartNanoTime)); } else { //返回Activity泄漏结果 return ActivityLeakResult.leakDetected(false, className, referenceChain, AnalyzeUtil.since(analysisStartNanoTime)); } } 复制代码
findPath
是发现引用链的核心方法
public Result findPath(Snapshot snapshot, Instance targetReference) { final List<Instance> targetRefList = new ArrayList<>(); targetRefList.add(targetReference); final Map<Instance, Result> results = findPath(snapshot, targetRefList); if (results == null || results.isEmpty()) { return new Result(null, false); } else { return results.get(targetReference); } } 复制代码
public Map<Instance, Result> findPath(Snapshot snapshot, Collection<Instance> targetReferences) { final Map<Instance, Result> results = new HashMap<>(); if (targetReferences.isEmpty()) { return results; } clearState(); //找到GCRoot对象,并放入队列中 enqueueGcRoots(snapshot); //是否忽略String对象 canIgnoreStrings = true; for (Instance targetReference : targetReferences) { if (isString(targetReference)) { canIgnoreStrings = false; break; } } final Set<Instance> targetRefSet = new HashSet<>(targetReferences); while (!toVisitQueue.isEmpty() || !toVisitIfNoPathQueue.isEmpty()) { ReferenceNode node; if (!toVisitQueue.isEmpty()) { node = toVisitQueue.poll(); } else { node = toVisitIfNoPathQueue.poll(); if (node.exclusion == null) { throw new IllegalStateException("Expected node to have an exclusion " + node); } } // Termination //找到完整引用链 GCRoot -> targetRef if (targetRefSet.contains(node.instance)) { results.put(node.instance, new Result(node, node.exclusion != null)); targetRefSet.remove(node.instance); if (targetRefSet.isEmpty()) { break; } } //当前节点是否已经查看过 if (checkSeen(node)) { continue; } if (node.instance instanceof RootObj) { //如果是GCRoot,按照GCRoot的规则查找子节点 visitRootObj(node); } else if (node.instance instanceof ClassObj) { //如果是Class,按照Class的规则查找子节点 visitClassObj(node); } else if (node.instance instanceof ClassInstance) { //如果是实例,按照实例的规则查找子节点 visitClassInstance(node); } else if (node.instance instanceof ArrayInstance) { //如果是数组,按照数组的规则查找子节点 visitArrayInstance(node); } else { throw new IllegalStateException("Unexpected type for " + node.instance); } } return results; } 复制代码
private void visitRootObj(ReferenceNode node) { RootObj rootObj = (RootObj) node.instance; Instance child = rootObj.getReferredInstance(); //Java栈帧中的局部变量 if (rootObj.getRootType() == RootType.JAVA_LOCAL) { Instance holder = HahaSpy.allocatingThread(rootObj); // We switch the parent node with the thread instance that holds // the local reference. Exclusion exclusion = null; if (node.exclusion != null) { exclusion = node.exclusion; } //将父节点替换为Thread(GCRoot), ReferenceNode parent = new ReferenceNode(null, holder, null, null, null); enqueue(exclusion, parent, child, "<Java Local>", LOCAL); } else { enqueue(null, node, child, null, null); } } 复制代码
private void visitClassObj(ReferenceNode node) { ClassObj classObj = (ClassObj) node.instance; Map<String, Exclusion> ignoredStaticFields = excludedRefs.staticFieldNameByClassName.get(classObj.getClassName()); for (Map.Entry<Field, Object> entry : classObj.getStaticFieldValues().entrySet()) { Field field = entry.getKey(); //不是引用类型,不会有下一层引用链;可以排查 if (field.getType() != Type.OBJECT) { continue; } String fieldName = field.getName(); if ("$staticOverhead".equals(fieldName)) { continue; } Instance child = (Instance) entry.getValue(); boolean visit = true; if (ignoredStaticFields != null) { Exclusion params = ignoredStaticFields.get(fieldName); if (params != null) { visit = false; if (!params.alwaysExclude) { enqueue(params, node, child, fieldName, STATIC_FIELD); } } } if (visit) { enqueue(null, node, child, fieldName, STATIC_FIELD); } } } 复制代码
private void visitClassInstance(ReferenceNode node) { ClassInstance classInstance = (ClassInstance) node.instance; Map<String, Exclusion> ignoredFields = new LinkedHashMap<>(); ClassObj superClassObj = classInstance.getClassObj(); Exclusion classExclusion = null; while (superClassObj != null) { Exclusion params = excludedRefs.classNames.get(superClassObj.getClassName()); if (params != null && (classExclusion == null || !classExclusion.alwaysExclude)) { // true overrides null or false. classExclusion = params; } Map<String, Exclusion> classIgnoredFields = excludedRefs.fieldNameByClassName.get(superClassObj.getClassName()); if (classIgnoredFields != null) { ignoredFields.putAll(classIgnoredFields); } superClassObj = superClassObj.getSuperClassObj(); } if (classExclusion != null && classExclusion.alwaysExclude) { return; } for (ClassInstance.FieldValue fieldValue : classInstance.getValues()) { Exclusion fieldExclusion = classExclusion; Field field = fieldValue.getField(); if (field.getType() != Type.OBJECT) { continue; } Instance child = (Instance) fieldValue.getValue(); String fieldName = field.getName(); Exclusion params = ignoredFields.get(fieldName); // If we found a field exclusion and it's stronger than a class exclusion if (params != null && (fieldExclusion == null || (params.alwaysExclude && !fieldExclusion.alwaysExclude))) { fieldExclusion = params; } enqueue(fieldExclusion, node, child, fieldName, INSTANCE_FIELD); } } 复制代码
private void visitArrayInstance(ReferenceNode node) { ArrayInstance arrayInstance = (ArrayInstance) node.instance; Type arrayType = arrayInstance.getArrayType(); //每个元素都是引用类型 if (arrayType == Type.OBJECT) { Object[] values = arrayInstance.getValues(); for (int i = 0; i < values.length; i++) { Instance child = (Instance) values[i]; enqueue(null, node, child, "[" + i + "]", ARRAY_ENTRY); } } } 复制代码
通过以上流程,一旦找到完整的引用链,就会跳出 findPath
方法的while循环,返回引用链。
Resource Canary还是有重复Bitmap检测的功能,位于 DuplicatedBitmapAnalyzer
中
public DuplicatedBitmapResult analyze(HeapSnapshot heapSnapshot) { final long analysisStartNanoTime = System.nanoTime(); try { final Snapshot snapshot = heapSnapshot.getSnapshot(); new ShortestDistanceVisitor().doVisit(snapshot.getGCRoots()); return findDuplicatedBitmap(analysisStartNanoTime, snapshot); } catch (Throwable e) { e.printStackTrace(); return DuplicatedBitmapResult.failure(e, AnalyzeUtil.since(analysisStartNanoTime)); } } 复制代码
findDuplicatedBitmap
这块逻辑比较复杂,暂时没看懂~~最终返回的 DuplicatedBitmapResult
中有一个 DuplicatedBitmapEntry
的list,这就是最后分析的结果。
public static class DuplicatedBitmapEntry implements Serializable { private final String mBufferHash; private final int mWidth; private final int mHeight; private final byte[] mBuffer; private final List<ReferenceChain> mReferenceChains; public DuplicatedBitmapEntry(int width, int height, byte[] rawBuffer, Collection<ReferenceChain> referenceChains) { mBufferHash = DigestUtil.getMD5String(rawBuffer); mWidth = width; mHeight = height; mBuffer = rawBuffer; mReferenceChains = Collections.unmodifiableList(new ArrayList<>(referenceChains)); } } 复制代码
至此,整个Resource Canary的线上分析流程就结束了。
总结
Resource Canary的Hprof文件分析逻辑,加深了对Java内存模型的理解。内存分析代码底层引用了 'com.squareup.haha:haha:2.0.3'
,想要深入原理需要再仔细阅读Haha这个库。后续有时间可以再深入研究。
以上所述就是小编给大家介绍的《Matrix源码分析————Resource Canary》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 以太坊源码分析(36)ethdb源码分析
- [源码分析] kubelet源码分析(一)之 NewKubeletCommand
- libmodbus源码分析(3)从机(服务端)功能源码分析
- [源码分析] nfs-client-provisioner源码分析
- [源码分析] kubelet源码分析(三)之 Pod的创建
- Spring事务源码分析专题(一)JdbcTemplate使用及源码分析
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
C Primer Plus
Stephen Prata、云巅工作室 / 云巅工作室 / 人民邮电出版社 / 2005-2-1 / 60.00元
《C Primer Plus(第5版)(中文版)》共17章。第1、2章学习C语言编程所需的预备知识。第3到15章介绍了C语言的相关知识,包括数据类型、格式化输入输出、运算符、表达式、流程控制语句、函数、数组和指针、字符串操作、内存管理、位操作等等,知识内容都针对C99标准;另外,第10章强化了对指针的讨论,第12章引入了动态内存分配的概念,这些内容更加适合读者的需求。第16章和第17章讨论了C预处......一起来看看 《C Primer Plus》 这本书的介绍吧!