Android art 虛擬機內(nèi)存管理

一 android art 內(nèi)存模型

理解art虛擬機內(nèi)存管理,需要先了解虛擬機的內(nèi)存組織,先看一下一個app運行時內(nèi)存分布情況如下圖所繪:

2.png
12c00000-12cc0000 rw-p 00000000 00:05 18389 /dev/ashmem/dalvik-main space (region space) (deleted)

12cc0000-13bc0000 ---p 000c0000 00:05 18389 /dev/ashmem/dalvik-main space (region space) (deleted)

13bc0000-32c00000 rw-p 00fc0000 00:05 18389 /dev/ashmem/dalvik-main space (region space) (deleted)

70589000-70c84000 rw-p 00000000 103:10 240  /data/dalvik-cache/arm/system@framework@boot.art

70c84000-7118a000 r--p 00000000 103:10 135 /data/dalvik-cache/arm/system@framework@boot.oat

7118a000-71d47000 r-xp 00506000 103:10 135 /data/dalvik-cache/arm/system@framework@boot.oat

71d47000-71d4e000 rw-p 00000000 00:00 0 [anon:.bss]

71d4e000-71d4f000 r--p 010c3000 103:10 135 /data/dalvik-cache/arm/system@framework@boot.oat

71d4f000-71d50000 rw-p 010c4000 103:10 135 /data/dalvik-cache/arm/system@framework@boot.oat

71d50000-71e04000 rw-p 00000000 00:05 18386 /dev/ashmem/dalvik-zygote space (deleted)

71e04000-71e05000 rw-p 00000000 00:05 21823 /dev/ashmem/dalvik-non moving space (deleted)

71e05000-75551000 ---p 00001000 00:05 21823 /dev/ashmem/dalvik-non moving space (deleted)

75d50000-75d50000 rw-p 0374d000 00:05 21823 /dev/ashmem/dalvik-non moving space (deleted)

12c00000 -32c00000 512M的heap空間,與[dalvik.vm.dex2oat-Xmx]: [512m] 正好對應(yīng)后面依次是boot.art的加載空間,屬于image space,緊跟著的是boat.oat的加載地址,Zygote分配以及預(yù)加載的地址(non-moving),在art/runtime/gc/space下是art的內(nèi)存管理代碼,對于各種內(nèi)存以及他們之間的關(guān)系可以通過下面的類繼承圖可以看出,imagespace largeobjectspace zygotespace 以及heap 使用的dlmallocaspace或者rosallocspace,不同的內(nèi)存使用不同的分配策略。還有一個region_space,這個是在cocurrent_copying的回收算法中使用。


1.png

Rosalloc是后面加的內(nèi)存分配算法,全稱runs-of-slots memory allocator。ROS allocator的基本分配單元是slot。slot大小從16Bytes到2048Bytes,分別是16,32,48,… n*16,512,1024,2048。應(yīng)該類似于內(nèi)核的伙伴管理算法。

二 堆內(nèi)存回收算法

先看一下art支持的回收算法

enum CollectorType {
  kCollectorTypeNone,
  kCollectorTypeMS,   mark-sweep 回收算法
  kCollectorTypeCMS, 并行mark-sweep 算法  
  kCollectorTypeSS,  // semi-space和mark-sweep 混合算法
  kCollectorTypeGSS,  分代kCollectorTypeSS.
  kCollectorTypeMC,  mark-compact算法
  // Heap trimming collector, doesn't do any actual collecting.
  kCollectorTypeHeapTrim,
  kCollectorTypeCC,  concurrent-copying算法
  // The background compaction of the concurrent copying collector.
  kCollectorTypeCCBackground, 
  // Instrumentation critical section fake collector.
  kCollectorTypeInstrumentation,
  // Fake collector for adding or removing application image spaces.
  kCollectorTypeAddRemoveAppImageSpace,
  // Fake collector used to implement exclusion between GC and debugger.
  kCollectorTypeDebugger,
  // A homogeneous space compaction collector used in background transition
  // when both foreground and background collector are CMS.
  kCollectorTypeHomogeneousSpaceCompact,
  // Class linker fake collector.
  kCollectorTypeClassLinker,
  // JIT Code cache fake collector.
  kCollectorTypeJitCodeCache,
  // Hprof fake collector.
  kCollectorTypeHprof,
  // Fake collector for installing/removing a system-weak holder.
  kCollectorTypeAddRemoveSystemWeakHolder,
  // Fake collector type for GetObjectsAllocated
  kCollectorTypeGetObjectsAllocated,
  // Fake collector type for ScopedGCCriticalSection
  kCollectorTypeCriticalSection,
};

Android支持的回收算法大體有
Mark-sweep算法:還分為Sticky, Partial, Full,根據(jù)是否并行,又分為ConCurrent和Non-Concurrent
MarkSweep::MarkSweep(Heap* heap, bool is_concurrent, const std::string& name_prefix)
mark_compact 算法:標(biāo)記-壓縮(整理)算法
concurrent_copying算法:
semi_space算法:

if (foreground_collector_type_ == kCollectorTypeCC) {
   region_space_ = space::RegionSpace::Create(kRegionSpaceName, region_space_mem_map);
} else if (IsMovingGc(foreground_collector_type_) &&
    foreground_collector_type_ != kCollectorTypeGSS) {
    bump_pointer_space_ = space::BumpPointerSpace::CreateFromMemMap("Bump pointer space 1",
                                                                    main_mem_map_1.release());
 } else {
    CreateMainMallocSpace(main_mem_map_1.release(), initial_size, growth_limit_, capacity_);
    if (foreground_collector_type_ == kCollectorTypeGSS) {
     bump_pointer_space_ = space::BumpPointerSpace::Create("Bump pointer space 1",
                                                            kGSSBumpPointerSpaceCapacity, nullptr);

從上面的代碼可以看出,如果是使用cocurrent_copying算法的話,使用RegionSpace分配空間。
如果使用的是移動回收算法同時前端回收算法不是kCollectorTypeGSS的話使用BumpPointerSpace分配空間。具體的回收算法如下:

  static bool IsMovingGc(CollectorType collector_type) {
    return
        collector_type == kCollectorTypeSS ||
        collector_type == kCollectorTypeGSS ||
        collector_type == kCollectorTypeCC ||
        collector_type == kCollectorTypeCCBackground ||
        collector_type == kCollectorTypeMC ||
        collector_type == kCollectorTypeHomogeneousSpaceCompact;
  }

GSS也使用BumpPointerSpace,只不過處理有所不同:
具體的回收算法和內(nèi)存分配算法之間的映射關(guān)系


2.png

三 android ART垃圾回收算法選擇

使用kill –s QUIT $PID 可以查看當(dāng)前虛擬機使用的垃圾回收算法,這個命令會在/data/anr下創(chuàng)建traces.txt文件有如下一行

Start Dumping histograms for 1 iterations for concurrent copying

上面這句話說明當(dāng)前使用的kCollectorTypeCC 算法
如何決定ART的回收算法,代碼在

heap_ = new gc::Heap(runtime_options.GetOrDefault(Opt::MemoryInitialSize),
                       // Override the collector type to CC if the read barrier config.
                       kUseReadBarrier ? gc::kCollectorTypeCC : xgc_option.collector_type_,
                       kUseReadBarrier ? BackgroundGcOption(gc::kCollectorTypeCCBackground)

其中kUseReadBarrier的定義為:

static constexpr bool kUseReadBarrier =
kUseBakerReadBarrier || kUseBrooksReadBarrier || kUseTableLookupReadBarrier;

如果kUseReadBarrier 為true的話,那么前端回收使用concurrent copying,后臺使用kCollectorTypeCCBackground。

{
    // If not set, background collector type defaults to homogeneous compaction.
    // If foreground is GSS, use GSS as background collector.
    // If not low memory mode, semispace otherwise.

    gc::CollectorType background_collector_type_;
    gc::CollectorType collector_type_ = (XGcOption{}).collector_type_;  // NOLINT [whitespace/braces] [5]
    bool low_memory_mode_ = args.Exists(M::LowMemoryMode);

    background_collector_type_ = args.GetOrDefault(M::BackgroundGc);
    {
      XGcOption* xgc = args.Get(M::GcOption);
      if (xgc != nullptr && xgc->collector_type_ != gc::kCollectorTypeNone) {
        collector_type_ = xgc->collector_type_;
      }
    }

    if (background_collector_type_ == gc::kCollectorTypeNone) {
      if (collector_type_ != gc::kCollectorTypeGSS) {
        background_collector_type_ = low_memory_mode_ ?
            gc::kCollectorTypeSS : gc::kCollectorTypeHomogeneousSpaceCompact;
      } else {
        background_collector_type_ = collector_type_;
      }
    }

    args.Set(M::BackgroundGc, BackgroundGcOption { background_collector_type_ });
  }

從上面的注釋可以看出,如果不設(shè)置參數(shù)的話,前端使用CMS算法,后端如果是low memory設(shè)備的話 使用kCollectorTypeSS 如下:

Start Dumping histograms for 1 iterations for marksweep + semispace

如果不是Low memory設(shè)備的話, 指定為homogeneous compact

Start Dumping histograms for 1 iterations for sticky concurrent mark sweep

如果前端是GSS的話,后端也使用GSS
如何手動設(shè)置ART的gc算法呢?
dalvik.vm.gctype和dalvik.vm.backgroundgctype 參數(shù)來控制

adb shell setprop dalvik.vm.gctype SS,preverify

四 堆內(nèi)存回收管理

3.png

通過上面圖,card_table Live_bitmap 和mark_bitmap都是對堆內(nèi)存的映射,這三個被用來管理堆內(nèi)存的回收。

    if (live_bitmap != nullptr && !space->IsRegionSpace()) {
      CHECK(mark_bitmap != nullptr);
      live_bitmap_->AddContinuousSpaceBitmap(live_bitmap);
      mark_bitmap_->AddContinuousSpaceBitmap(mark_bitmap);
    }
} else {
    CHECK(space->IsDiscontinuousSpace());
    space::DiscontinuousSpace* discontinuous_space = space->AsDiscontinuousSpace();
    live_bitmap_->AddLargeObjectBitmap(discontinuous_space->GetLiveBitmap());
    mark_bitmap_->AddLargeObjectBitmap(discontinuous_space->GetMarkBitmap());
    discontinuous_spaces_.push_back(discontinuous_space);
}
// Allocate the card table.
  // We currently don't support dynamically resizing the card table.
  // Since we don't know where in the low_4gb the app image will be located, make the card table
  // cover the whole low_4gb. TODO: Extend the card table in AddSpace.
  UNUSED(heap_capacity);
  // Start at 64 KB, we can be sure there are no spaces mapped this low since the address range is
  // reserved by the kernel.
  static constexpr size_t kMinHeapAddress = 4 * KB;
  card_table_.reset(accounting::CardTable::Create(reinterpret_cast<uint8_t*>(kMinHeapAddress),
                                                  4 * GB - kMinHeapAddress));

從注釋看,card_table映射整個4G空間,其中card_table的結(jié)構(gòu)如下:

// Maintain a card table from the the write barrier. All writes of
// non-null values to heap addresses should go through an entry in
// WriteBarrier, and from there to here.
class CardTable {
 public:
  static constexpr size_t kCardShift = 10;
  static constexpr size_t kCardSize = 1 << kCardShift;
  static constexpr uint8_t kCardClean = 0x0;
  static constexpr uint8_t kCardDirty = 0x70;
  static constexpr uint8_t kCardAged = kCardDirty - 1;

一個cardtable使用一個字節(jié)空間標(biāo)記,這一個字節(jié)的值是GC_CARD_CLEAN或者 GC_CARD_DIRTY。一個字節(jié)表示1<< kCardShift也就是1k的空間,也就是映射4G左右的空間,card_table大約占4M左右的空間。那么card_table到底是做什么的呢? 在并行回收算法中,為了快速定位object的變化,挨個掃描棧進行遍歷這種耗時又費力的方法顯然不能滿足需求,因此就有了card_table這種快速定位那些object修改,遍歷object后標(biāo)記回收。針對card_table的管理引入了ModUnionTable 和RememberedSet,其中ModUnionTable主要是管理zygote的空間和Image space,rememberdset則主要管理no_moving space通過ProcessCards的處理我們可以看到。

void Heap::ProcessCards(TimingLogger* timings,
                        bool use_rem_sets,
                        bool process_alloc_space_cards,
                        bool clear_alloc_space_cards) {
  TimingLogger::ScopedTiming t(__FUNCTION__, timings);
  // Clear cards and keep track of cards cleared in the mod-union table.
  for (const auto& space : continuous_spaces_) {
    accounting::ModUnionTable* table = FindModUnionTableFromSpace(space);
    accounting::RememberedSet* rem_set = FindRememberedSetFromSpace(space);
    if (table != nullptr) {
      const char* name = space->IsZygoteSpace() ? "ZygoteModUnionClearCards" :
          "ImageModUnionClearCards";
      TimingLogger::ScopedTiming t2(name, timings);
      table->ProcessCards();  //針對modunio_table掃描dirty,放入到dirty_card中
    } else if (use_rem_sets && rem_set != nullptr) {
      DCHECK(collector::SemiSpace::kUseRememberedSet && collector_type_ == kCollectorTypeGSS)
          << static_cast<int>(collector_type_);
      TimingLogger::ScopedTiming t2("AllocSpaceRemSetClearCards", timings);
      rem_set->ClearCards();
    } else if (process_alloc_space_cards) {
      TimingLogger::ScopedTiming t2("AllocSpaceClearCards", timings);
      if (clear_alloc_space_cards) {
        uint8_t* end = space->End();
        if (space->IsImageSpace()) {
          // Image space end is the end of the mirror objects, it is not necessarily page or card
          // aligned. Align up so that the check in ClearCardRange does not fail.
          end = AlignUp(end, accounting::CardTable::kCardSize);
        }
        card_table_->ClearCardRange(space->Begin(), end);
      } else {
        // No mod union table for the AllocSpace. Age the cards so that the GC knows that these
        // cards were dirty before the GC started.
        // TODO: Need to use atomic for the case where aged(cleaning thread) -> dirty(other thread)
        // -> clean(cleaning thread).
        // The races are we either end up with: Aged card, unaged card. Since we have the
        // checkpoint roots and then we scan / update mod union tables after. We will always
        // scan either card. If we end up with the non aged card, we scan it it in the pause.
        card_table_->ModifyCardsAtomic(space->Begin(), space->End(), AgeCardVisitor(),
                                       VoidFunctor());
      }
    }
  }
}

1 針對modunio_table 執(zhí)行ProcessCards()

void ModUnionTableCardCache::ProcessCards() {
  CardTable* const card_table = GetHeap()->GetCardTable();
  ModUnionAddToCardBitmapVisitor visitor(card_bitmap_.get(), card_table);
  // Clear dirty cards in the this space and update the corresponding mod-union bits.
  card_table->ModifyCardsAtomic(space_->Begin(), space_->End(), AgeCardVisitor(), visitor);
}
主要是將修改的table添加到bitmap_中
class ModUnionAddToCardBitmapVisitor {
 public:
  ModUnionAddToCardBitmapVisitor(ModUnionTable::CardBitmap* bitmap, CardTable* card_table)
      : bitmap_(bitmap), card_table_(card_table) {}

  inline void operator()(uint8_t* card,
                         uint8_t expected_value,
                         uint8_t new_value ATTRIBUTE_UNUSED) const {
    if (expected_value == CardTable::kCardDirty) {
      // We want the address the card represents, not the address of the card.
      bitmap_->Set(reinterpret_cast<uintptr_t>(card_table_->AddrFromCard(card)));
    }
  }

2 針對remember_set,執(zhí)行ClearCards(),添加到dirty_cards_中

void RememberedSet::ClearCards() {
  CardTable* card_table = GetHeap()->GetCardTable();
  RememberedSetCardVisitor card_visitor(&dirty_cards_);
  // Clear dirty cards in the space and insert them into the dirty card set.
  card_table->ModifyCardsAtomic(space_->Begin(), space_->End(), AgeCardVisitor(), card_visitor);
}

3 現(xiàn)在只針對mark_sweep算法來看其他space在ProcessCards中,clear_alloc_space_cards為true,card_table_->ClearCardRange(space->Begin(), end);主要工作是清除card_table。如果是stick_mark_sweep的話,只是進行age操作

        card_table_->ModifyCardsAtomic(space->Begin(), space->End(), AgeCardVisitor(),
                                       VoidFunctor());

第一章中有描述,一個java進程的空間分為三大類zygote+image空間 non-moving space空間 和main space空間,通過??臻g的掃描markroots來標(biāo)記main_space空間的引用,但是對于zygote non-moving space空間的話,需要通過card_table標(biāo)記他們的引用。最后通過比較live_bitmap和mark_bitmap的區(qū)別來找到那些已經(jīng)不被使用的Ojbect回收。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容