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

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的回收算法中使用。

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)系

三 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)存回收管理

通過上面圖,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回收。