Matrix源碼分析————Resource Canary

概述

年前,微信開源了Matrix項目,提供了Android、ios的APM實現方案。對於Android端實現,主要包括APK CheckerResource CanaryTrace CanarySQLite LintIO Canary五部分。本文主要介紹Resource Canary的源碼實現,其餘部分的源碼分析將在後續推出。java

代碼框架分析

Resource Canary主要是用來檢測Activit級別的內存泄漏、以及重複建立的冗餘Bitmap。總體代碼分爲兩部分:客戶端檢測內存泄漏、裁剪Hprof文件,服務端分析回傳的Hprof文件。node

客戶端監控內存泄漏、裁剪Hprof文件

這部分代碼位於matrix-resource-canary-android模塊下。監控Activity泄漏的大體流程以下:android

  • 經過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>
複製代碼

CanaryWorkerServiceCanaryResultService都是在獨立進程運行的。其中CanaryWorkerService主要執行doShrinkHprofAndReport方法:ios

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);
        }
    }
複製代碼

裁剪的核心代碼以下:git

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));
            //一、收集Bitmap和String信息
            reader.accept(new HprofInfoCollectVisitor());
            // Reset.
            is.getChannel().position(0);
            //二、找到Bitmap、String中持有的byte數組,並找到內容重複的Bitmap
            reader.accept(new HprofKeptBufferCollectVisitor());
            // Reset.
            is.getChannel().position(0);
            //三、裁剪掉內容重複的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數組)。github

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中app

@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這個庫。後續有時間能夠再深刻研究。

相關文章
相關標籤/搜索