博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Matrix源码分析————Resource Canary
阅读量:6437 次
发布时间:2019-06-23

本文共 42945 字,大约阅读时间需要 143 分钟。

概述

年前,微信开源了项目,提供了Android、ios的APM实现方案。对于Android端实现,主要包括APK CheckerResource CanaryTrace CanarySQLite LintIO 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 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
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);    }复制代码
复制代码

CanaryWorkerServiceCanaryResultService都是在独立进程运行的。其中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
> idDataSet = mBufferIdToElementDataMap.entrySet(); final Map
duplicateBufferFilterMap = new HashMap<>(); for (Map.Entry
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类的静态属性

常见的就是这几种,完整说明可以查看

在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
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
keysFound = new ArrayList<>(); //遍历DestroyedActivityInfo的所有实例 for (Instance infoInstance : infoClass.getInstancesList()) { final List
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
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
targetRefList = new ArrayList<>(); targetRefList.add(targetReference); final Map
results = findPath(snapshot, targetRefList); if (results == null || results.isEmpty()) { return new Result(null, false); } else { return results.get(targetReference); } }复制代码
public Map
findPath(Snapshot snapshot, Collection
targetReferences) { final Map
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
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, "
", LOCAL); } else { enqueue(null, node, child, null, null); } }复制代码
private void visitClassObj(ReferenceNode node) {        ClassObj classObj = (ClassObj) node.instance;        Map
ignoredStaticFields = excludedRefs.staticFieldNameByClassName.get(classObj.getClassName()); for (Map.Entry
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
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
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
mReferenceChains; public DuplicatedBitmapEntry(int width, int height, byte[] rawBuffer, Collection
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这个库。后续有时间可以再深入研究。

转载于:https://juejin.im/post/5cc485936fb9a031f41607a8

你可能感兴趣的文章
nfs永久挂载与临时挂载
查看>>
linux查看网络链接状况命令之-netstat
查看>>
我的友情链接
查看>>
UIView的layoutSubviews和drawRect方法何时调用
查看>>
mysql主从同步
查看>>
制作最简化的Linux系统
查看>>
我的友情链接
查看>>
使用List的remove方法需要的注意的问题
查看>>
Ansible的介绍、安装、配置及常用模块介绍
查看>>
编码列表
查看>>
eigrp 配置
查看>>
谈一谈 redis 集群
查看>>
concurrent包
查看>>
在Linux下调试Python代码的各种方法
查看>>
centos7塔建MQ服务器
查看>>
Peer authentication failed for user
查看>>
超强的.NET图像工具包VintaSoftImaging.NET SDK更新至v8.6丨75折优惠
查看>>
阿里云上Kubernetes集群联邦
查看>>
洛谷2219:[HAOI2007]修筑绿化带——题解
查看>>
监控webservice信息
查看>>