art虚拟机分配对象过程

1,000 阅读5分钟

跟dalvik很相似。只是gc不一样,art虚拟机gc

mirror::Object* Heap::AllocateInternalWithGc(Thread* self, space::AllocSpace* space,  
                                             size_t alloc_size, size_t* bytes_allocated) {  
  mirror::Object* ptr;  
  
  // The allocation failed. If the GC is running, block until it completes, and then retry the  
  // allocation.  
  collector::GcType last_gc = WaitForConcurrentGcToComplete(self);  
  if (last_gc != collector::kGcTypeNone) {  
    // A GC was in progress and we blocked, retry allocation now that memory has been freed.  
    ptr = TryToAllocate(self, space, alloc_size, false, bytes_allocated);  
    if (ptr != NULL) {  
      return ptr;  
    }  
  }  
  
  // Loop through our different Gc types and try to Gc until we get enough free memory.  
  for (size_t i = static_cast<size_t>(last_gc) + 1;  
      i < static_cast<size_t>(collector::kGcTypeMax); ++i) {  
    bool run_gc = false;  
    collector::GcType gc_type = static_cast<collector::GcType>(i);  
    switch (gc_type) {  
      case collector::kGcTypeSticky: {  
          const size_t alloc_space_size = alloc_space_->Size();  
          run_gc = alloc_space_size > min_alloc_space_size_for_sticky_gc_ &&  
              alloc_space_->Capacity() - alloc_space_size >= min_remaining_space_for_sticky_gc_;  
          break;  
        }  
      case collector::kGcTypePartial:  
        run_gc = have_zygote_space_;  
        break;  
      case collector::kGcTypeFull:  
        run_gc = true;  
        break;  
      default:  
        break;  
    }  
  
    if (run_gc) {  
      // If we actually ran a different type of Gc than requested, we can skip the index forwards.  
      collector::GcType gc_type_ran = CollectGarbageInternal(gc_type, kGcCauseForAlloc, false);  
      DCHECK_GE(static_cast<size_t>(gc_type_ran), i);  
      i = static_cast<size_t>(gc_type_ran);  
  
      // Did we free sufficient memory for the allocation to succeed?  
      ptr = TryToAllocate(self, space, alloc_size, false, bytes_allocated);  
      if (ptr != NULL) {  
        return ptr;  
      }  
    }  
  }  
  
  // Allocations have failed after GCs;  this is an exceptional state.  
  // Try harder, growing the heap if necessary.  
  ptr = TryToAllocate(self, space, alloc_size, true, bytes_allocated);  
  if (ptr != NULL) {  
    return ptr;  
  }  
  
  ......  
  
  // We don't need a WaitForConcurrentGcToComplete here either.  
  CollectGarbageInternal(collector::kGcTypeFull, kGcCauseForAlloc, true);  
  return TryToAllocate(self, space, alloc_size, true, bytes_allocated);  
}  

这个函数定义在文件art/runtime/gc/heap.cc中。

Heap类的成员函数AllocateInternalWithGc主要是通过垃圾回收来满足请求分配的内存,它的执行逻辑如下所示:

  1. 调用Heap类的成员函数WaitForConcurrentGcToComplete检查是否有并行GC正在执行。如果有的话,就等待其执行完成,并且得到它的类型last_gc。如果last_gc如果不等于collector::kGcTypeNone,就表示有并行GC并且已经执行完成,因此就可以调用Heap类的成员函数TryToAllocate在不增长当前堆大小的前提下再次尝试分配请求的内存了。如果分配成功,则返回得到的内存起始地址给调用者。否则的话,继续往下执行。
  2. 依次执行kGcTypeSticky、kGcTypePartial和kGcTypeFull三种类型的GC。每次GC执行完毕,都尝试调用Heap类的成员函数TryToAllocate在不增长当前堆大小的前提下再次尝试分配请求的内存。如果分配内存成功,则返回得到的内存起始地址给调用者,并且不再执行下一种类型的GC。

这里需要注意的一点是,kGcTypeSticky、kGcTypePartial和kGcTypeFull三种类型的GC的垃圾回收力度是依次加强:kGcTypeSticky只回收上次GC后在Allocation Space中新分配的垃圾对象;kGcTypePartial只回收Allocation Space的垃圾对象;kGcTypeFull同时回收Zygote Space和Allocation Space的垃圾对象。通过这种策略,就有可能以最小代价解决分配对象时遇到的内在不足问题。不过,对于类型为kGcTypeSticky和kGcTypePartial的GC,它们的执行有前提条件的。

类型为kGcTypeSticky的GC的执行代码虽然是最小的,但是它能够回收的垃圾也是最小的。如果回收的垃圾不足于满足请求分配的内存,那就相当于做了一次无用功了。因此,执行类型为kGcTypeSticky的GC需要满足两个条件。第一个条件是上次GC后在Allocation Space上分配的内存要达到一定的阀值,这样才有比较大的概率回收到较多的内存。第二个条件Allocation Space剩余的未分配内存要达到一定的阀值,这样可以保证在回收得到较少内存时,也有比较大的概率满足请求分配的内存。前一个阀值定义在Heap类的成员变量min_alloc_space_size_for_sticky_gc_中,它的值设置为2M,而上次GC以来分配的内存通过当前Allocation Space的大小估算得到,即通过调用Heap类的成员变量alloc_space_指向的一个DlMallocSpace对象的成员函数Size获得。后一个阀值定义在Heap类的成员变量min_remaining_space_for_sticky_gc_中,它的值设置为1M,而Allocation Space剩余的未分配内存可以用Allocation Space的总大小减去当前Allocation Space的大小得到。通过调用Heap类的成员变量alloc_space_指向的一个DlMallocSpace对象的成员函数Capacity获得其总大小。

类型为kGcTypePartial的GC的执行前提是已经从Zygote Space中划分出Allocation Space。从前面ART运行时Java堆创建过程分析一文可以知道,当Heap类的成员变量have_zygote_space_的值等于true时,就表明已经从Zygote Space中划分出Allocation Space了。因此,在这种情况下,就可以执行类型为kGcTypePartial的GC了。

每一种类型的GC都是通过调用Heap类的成员函数CollectGarbageInternal来执行。注意这时候调用Heap类的成员函数CollectGarbageInternal传递的第三个参数为false,表示不对那些只被软引用对象引用的对象进行回收。如果上述的三种类型的GC执行完毕,还是不能满足分配请求的内存,则继续往下执行。
3. 经过前面三种类型的GC后还是不能成功分配到内存,那就说明能够回收的内存还是太小了,因此,这时候只能通过在允许范围内增长堆的大小来满足内存分配请求了。前面分析Heap类的成员函数TryToAllocate时,将第四个参数设置为true,即可在允许范围内增长堆大小的前提下进行内存分配。如果在允许范围内增长了堆的大小还是不能成功分配到请求的内存,那就只能出最后的一个大招了。
4. 最后的大招是首先执行一个类型为kGcTypeFull的、要求回收那些只被软引用对象引用的对象的GC,接着再在允许范围内增长堆大小的前提下尝试分配内存。这一次如果还是失败,那就真的是内存不足了。

至此,我们就对ART运行时在堆上为新创建对象分配内存的过程分析完成了。从中我们就可以看到,ART运行时面临的最大挑战就是内存不足问题,它要通过在允许范围内增长堆大小以及垃圾回收两个手段来解决。其中,垃圾回收会对程序造成影响,因此在执行垃圾回收时,使用的力度要从小到大。