Background

内存泄露问题由于GC中的引用引起,参考Art源码,简单分析下GC机制,并给出Android中的内存泄露分析方法。

GC: Mark and Sweep

avatar

Mark

Marking时根据性能不同有几个策略,简要分析下,marking phase,有几个步骤:

1. mark roots     // mark invalidate root object
2. mark reachable // remark and mark instances in mark stack
3. 其它

Marking调用栈可参考:

ConcurrentCopying::MarkingPhase() or MarkSweep::MarkingPhase() or SemiSpace::MarkingPhase()
collector::ConcurrentCopying::RunPhases() or collector::MarkSweep::RunPhases() or collector::SemiSpace::RunPhases()
art::gc::collector::GarbageCollector::Run(art::gc::GcCause, bool)
art::gc::Heap::CollectGarbageInternal(art::gc::collector::GcType, art::gc::GcCause, bool)

下面代码展示MarkSweep方式,标记的Root都有哪些,并可以看出GC都管理哪些空间。

// art::runtime.cc
void Runtime::VisitRoots(RootVisitor* visitor, VisitRootFlags flags) {
  VisitNonConcurrentRoots(visitor, flags);
  VisitConcurrentRoots(visitor, flags);
}

void Runtime::VisitNonConcurrentRoots(RootVisitor* visitor, VisitRootFlags flags) {
  // VisitThreadRoots(visitor, flags);
  thread_list_->VisitRoots(visitor, flags);

  // VisitNonThreadRoots(visitor);
  java_vm_->VisitRoots(visitor);
  // ...
  pre_allocated_NoClassDefFoundError_.VisitRootIfNonNull(visitor, RootInfo(kRootVMInternal));
  VisitImageRoots(visitor);
  verifier::ClassVerifier::VisitStaticRoots(visitor);
  VisitTransactionRoots(visitor);
}

void Runtime::VisitConcurrentRoots(RootVisitor* visitor, VisitRootFlags flags) {
  intern_table_->VisitRoots(visitor, flags);
  class_linker_->VisitRoots(visitor, flags);
  jni_id_manager_->VisitRoots(visitor);
  heap_->VisitAllocationRecords(visitor);
  if ((flags & kVisitRootFlagNewRoots) == 0) {
    // Guaranteed to have no new roots in the constant roots.
    VisitConstantRoots(visitor);
  }
}

Sweep

avatar

这里讨论下最简单的sweep,即在分配空间(allocate)时调用sweep,遍历live_bitmap_,live_bitmap_[i]与mark_bitmap_[i]比对,找出garbage,并对garbage的space->Free(obj)。

// art::runtime.cc
void Runtime::SweepSystemWeaks(IsMarkedVisitor* visitor) {
  GetInternTable()->SweepInternTableWeaks(visitor);
  GetMonitorList()->SweepMonitorList(visitor);
  GetJavaVM()->SweepJniWeakGlobals(visitor);
  GetHeap()->SweepAllocationRecords(visitor);
  if (GetJit() != nullptr) {
    // Visit JIT literal tables. Objects in these tables are classes and strings
    // and only classes can be affected by class unloading. The strings always
    // stay alive as they are strongly interned.
    // TODO: Move this closer to CleanupClassLoaders, to avoid blocking weak accesses
    // from mutators. See b/32167580.
    GetJit()->GetCodeCache()->SweepRootTables(visitor);
  }
  thread_list_->SweepInterpreterCaches(visitor);

  // All other generic system-weak holders.
  for (gc::AbstractSystemWeakHolder* holder : system_weak_holders_) {
    holder->Sweep(visitor);
  }
}

GC: Heap

内存管理中的Heap是指地址顺序变大的数据结构,为了更快实现对Heap的GC等操作,Heap实现多种数据结构的映射。art::heap.cc给出了它的实现。

部分方法:

部分成员:

Heap dump

快照Heap时,使用Heap中的AllocRecordObjectMap,它的数据结构包含了List<GcRoot>的成员。

// art::allocation_record.h
class AllocRecordObjectMap {
  public:
  // GcRoot<mirror::Object> pointers in the list are weak roots, and the last recent_record_max_
  // number of AllocRecord::klass_ pointers are strong roots (and the rest of klass_ pointers are
  // weak roots). The last recent_record_max_ number of pairs in the list are always kept for DDMS's
  // recent allocation tracking, but GcRoot<mirror::Object> pointers in these pairs can become null.
  // Both types of pointers need read barriers, do not directly access them.
  using EntryPair = std::pair<GcRoot<mirror::Object>, AllocRecord>;
  typedef std::list<EntryPair> EntryList;
  // EntryList中,index小于recent_record_max_的元素是weak reference,否则是stronge reference。
}

Memory leak

AllocRecordObjectMap会存储每个对象的GcRoot,如果某个应该删除的实例一直连接着GcRoot,则该对象不会被GC删除,这样的对象积累多了,会产生这样的问题:一,GC每次遍历标记,删除的时间复杂度增加;二,分配对象的时间复杂度增加;三,会引起OutOfMemory。

在Android应用中,系统中有生命周期的变量实例,用户生成的静态实例,都由PathClassLoader加载,是PathClassLoader的节点,PathClassLoader是Heap中的一个Root,一些该Root下的系统实例,像Activity,有自己的释放机制(例如:Activity.onDestory),会从PathClassLoader中删除自己的引用,但生成的静态实例,将一直被PathClassLoader持有,如果该静态实例持有Activity,就会间接被PathClassLoader持有,不被回收。

avatar

Find memory leak

确定存在内存泄露的方法有很多,这里略,找到问题原因可参考下面步骤:

  1. Dump当前应用的内存快照(adb shell am dumpheap pid/pkgName file)

  2. 用Android Studio打开这个文件

  3. 使用Profiler中的Activity/Fragment Leaks分析

  4. 根据References的Depth找到引用Context的GcRoot,分析业务逻辑,得到解决方案。

GC伪代码

为了理解GC,实现了简单的伪代码

public class GC {
    private int count;
    private int[] marks;
    private Object[] objects;
    private int max;
    private int ptr;
    private final static int UNMARKED = 0;
    private final static int YOUNG = 1;
    private final static int OLD = 2;
    public GC(int max) {
        this.max = max;
        this.marks = new int[max];
        this.objects = new Object[max];
    }
    private int allocateInternal(Object obj) {
        if (count >= max) {
            return -1;
        }
        if(max - count < max / 3) { // assumed no space for allocation, and it will failed;
            gc();
        }
        count++;
        objects[ptr++ % max] = obj;
        return ptr;
    }
    public int allocate(Object obj) {
        int ptr = allocateInternal(obj);
        if(ptr >= 0) {
            return ptr;
        }
        // gc, and retry allocation
        gc();

        ptr = allocateInternal(obj);
        if(ptr >= 0) {
            return ptr;
        }
        // heap is really full.
        throw new OutOfMemoryError();
    }
    private void sweep() {
        // O(max)
        for (int i = 0; i < max; i++) {
            if (marks[i] == OLD) {
                count--;
                objects[i] = null;
                marks[i] = UNMARKED;
            }
        }
    }
    private void mark() {
        // O(max)
        for (int i = 0; i < max; i++) {
            if (marks[i] == UNMARKED && objects[i] != null) {
                marks[i] = YOUNG;
            } else if (marks[i] == YOUNG) {
                marks[i] = OLD;
            }
        }
    }
    private void gc() {
        mark();
        sweep();
        System.out.println("Concurrent GC.");
    }
    public static void main(String[] args) {
        GC gc = new GC(10);
        for (int i = 0; i < 20; i++) {
            gc.allocate("" + i);
        }
    }
}

最后,Google ART代码写的挺清晰的,另外这些技术资料有参考和自己的理解,如有不对请指正,谢谢。

参考: