细节点 — 5

LeakCanary是如何找到泄漏的对象,以及最短的对象引用路径的呢?

功能的入口是在HeapAnalyzerService里,调用到HeapAnalyzer类:

@Override protected void onHandleIntent(Intent intent) {
    if (intent == null) {
      CanaryLog.d("HeapAnalyzerService received a null intent, ignoring.");
      return;
    }
    String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA);
    HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA);

    HeapAnalyzer heapAnalyzer = new HeapAnalyzer(heapDump.excludedRefs);

    AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey);
    AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
  }

1、分析Dump文件

首先是调用HAHA的库分析Dump文件:

HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
HprofParser parser = new HprofParser(buffer);
Snapshot snapshot = parser.parse();

Dump文件是来自上面HeapDumper类。

文件的读取

MemoryMappedFileBuffer读取文件采用了NIO的channel的方式,这种的效率是很高的。

FileInputStream inputStream = new FileInputStream(f);
        try {
            long offset = 0;
            for (int i = 0; i < shards; i++) {
                long size = Math.min(mLength - offset, mBufferSize + mPadding);
                mByteBuffers[i] = inputStream.getChannel()
                        .map(FileChannel.MapMode.READ_ONLY, offset, size);
                mByteBuffers[i].order(HPROF_BYTE_ORDER);
                offset += mBufferSize;
            }
            mCurrentPosition = 0;
        } finally {
            inputStream.close();
        }

写了一个对比的文件读取代码:

// 采用chanel的方式
new Thread(new Runnable() {
            @Override
            public void run() {
                File f = new File(MainActivity.this.getFilesDir(), "test");
                try {
                    long start = System.currentTimeMillis();
                    FileInputStream inputStream = new FileInputStream(f);
                        mByteBuffers = inputStream.getChannel()
                                .map(FileChannel.MapMode.READ_ONLY, 0, 20480000);
                    ByteBuffer b = mByteBuffers.asReadOnlyBuffer();
                    Log.e("111", (System.currentTimeMillis() - start) + "++++++++++++++");
                    String s = getString(b);
                    inputStream.close();
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();

// 采用传统的方式
        new Thread(new Runnable() {
            @Override
            public void run() {
                File f = new File(MainActivity.this.getFilesDir(), "test");
                try {
                    long start = System.currentTimeMillis();
                    FileInputStream inputStream = new FileInputStream(f);
                    byte[] b = new byte[20480000];
                    inputStream.read(b, 0, 20480000);
                    inputStream.close();
                    Log.e("111", (System.currentTimeMillis() - start) + "==============");
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();

测试的结果:

相应的,写文件采用channel的方式也是更快一些。

2、找出泄漏的对象

调用findLeakingReference()方法。

核心的代码:

private Instance findLeakingReference(String key, Snapshot snapshot) {
    ClassObj refClass = snapshot.findClass(KeyedWeakReference.class.getName());
    List<String> keysFound = new ArrayList<>();
    for (Instance instance : refClass.getInstancesList()) {
      List<ClassInstance.FieldValue> values = classInstanceValues(instance);
      String keyCandidate = asString(fieldValue(values, "key"));
      if (keyCandidate.equals(key)) {
        return fieldValue(values, "referent");
      }
      keysFound.add(keyCandidate);
    }
    throw new IllegalStateException(
        "Could not find weak reference with key " + key + " in " + keysFound);
  }

在这里找到发生内存泄漏的对象,即相当于是根的对象。通过泄漏对象的WeakReference在Snaphot中定位它

3、寻找泄漏的引用的最短路径

在确定了泄漏的对象之后,接下来就要找到一条道泄漏对象的最短引用路径。这个是在findLeakTrace()方法来完成。

大致的原理:

整个内存的信息被抽象成了以GCRoot为根的树状结构,由于已经知道了泄漏的对象,因此它在树中的位置是可以确定的。

因此采用广度优先的搜索策略,从被泄漏的对象开始,加入到FIFO队列中,一层层的往上面搜索,哪条路径最先到达GCRoot就表示这是最短路径。

上面的算法实现是在ShortestPathFinder类里面,完成了上面最短路径后,接下来就调用buildLeakTrace构建查询结果,将最短路径转换成最后用于显示的LeakTrace对象,它包括了路径上各节点LeakTraceElement组成的链表,代表了泄漏对象的最短路径。 最后将结果封装成AnalysisResult对象,传递到DisplayLeakService进行处理。

results matching ""

    No results matching ""