Art 虛擬機(jī)系列Heap內(nèi)存模型分配策略詳解
背景
隨著性能優(yōu)化逐步進(jìn)入深水區(qū),我們發(fā)現(xiàn)很多大廠的方案,其實(shí)都建立在對(duì)正常虛擬機(jī)運(yùn)行過(guò)程中Hook的基礎(chǔ)上,建立自己的優(yōu)化方案。因此,只有了解了art虛擬機(jī)的運(yùn)行機(jī)制,才能真正去實(shí)施一些特殊的“魔改”方案。因?yàn)閍rt代碼非常龐大,我們會(huì)從很多的方面去了解他。本片是Heap 模型的第一篇。
內(nèi)存分配
當(dāng)我們?cè)趈ava層 new 一個(gè)對(duì)象的時(shí)候,由于有虛擬機(jī)的存在,我們開(kāi)發(fā)者不用了解任何相關(guān)的內(nèi)存分配細(xì)節(jié),就能夠完成一次內(nèi)存的分配。那么,art虛擬機(jī)究竟是怎么幫助我們進(jìn)行內(nèi)存分配的,其實(shí)得益于art虛擬機(jī)抽象的以下分配類

我們從上圖可以看到,art把內(nèi)存分配相關(guān)的模型策略,分為了6層
第一層
分為了Space與AllocSpace兩個(gè)類,他們都是一個(gè)虛類,即需要子類去實(shí)現(xiàn)virtual修飾的方法,比如我們看到Space類定義
class Space {
public:
// Dump space. Also key method for C++ vtables.
virtual void Dump(std::ostream& os) const;
Space代表一塊內(nèi)存空間,AllocSpace代表一塊可用于內(nèi)存分配的空間,AllocSpace則提供了內(nèi)存分配與釋放相關(guān)的虛函數(shù),Alloc,F(xiàn)ree,由特定的子類去實(shí)現(xiàn)對(duì)應(yīng)的策略。
class AllocSpace {
public:
virtual mirror::Object* Alloc(Thread* self, size_t num_bytes, size_t* bytes_allocated,
size_t* usable_size, size_t* bytes_tl_bulk_allocated) = 0;
virtual size_t Free(Thread* self, mirror::Object* ptr) = 0;
再回過(guò)頭我們看到上面的類圖,我們就會(huì)發(fā)現(xiàn)子類中的ImageSpace是沒(méi)有繼承于AllocSpace,這就意味著,它其實(shí)是沒(méi)有實(shí)現(xiàn)對(duì)應(yīng)的Alloc與Free相關(guān)的函數(shù),因此,我們常說(shuō)的GC機(jī)制,其實(shí)是不會(huì)作用在這塊內(nèi)存空間上面的!非常有趣!我們下文會(huì)繼續(xù)講到,這跟art劃分的內(nèi)存模型有很大關(guān)系
第二層
第二層是ContinuousSpace與DiscontinuousSpace兩個(gè)類,這兩個(gè)類的職責(zé)非常簡(jiǎn)單,前者就是代表了該內(nèi)存空間是連續(xù)的,后者是非連續(xù)。
劃分為連續(xù)與非連續(xù),其實(shí)更大的作用是在GC過(guò)程中相關(guān)的內(nèi)存整理,非連續(xù)的空間有更簡(jiǎn)單的分配方式
第三層
MemMapSpace 與 LargeObjectSpace是第三層,在這里,已經(jīng)開(kāi)始根據(jù)外部所需要的空間的大小,進(jìn)行了不同的空間劃分。比如當(dāng)分配的內(nèi)存屬于java對(duì)象String或者是基礎(chǔ)類型的數(shù)組,且內(nèi)存超過(guò)3個(gè)內(nèi)存頁(yè)(Heap 構(gòu)造時(shí)我們會(huì)講到,一般是12kb)的時(shí)候,就會(huì)把資源分配到LargeObject內(nèi)存空間里面,其他的就分配在MemMapSpace
第四層
到了第四層,我們出現(xiàn)了第一個(gè)非常重要的內(nèi)存分配空間,ImageSpcace,ImageSpace用于.art文件的加載,我們知道應(yīng)用可以在預(yù)編譯的時(shí)候,生成.art文件,還有就是一些系統(tǒng)類,比如boot.art,這些art文件在內(nèi)存中的表現(xiàn),其實(shí)就放在了ImageSpace中。同時(shí)我們也講到,ImageSpace沒(méi)有繼承AllocSpace,這也就意味著,它不會(huì)對(duì)外提供分配與釋放策略,因此該內(nèi)存中,也不會(huì)被GC所影響?。梢园巡糠謫?dòng)類放在這里,達(dá)到GC抑制的效果)而另外的ContinuousMemMapAllocSpace,將由它的子類去實(shí)現(xiàn)常見(jiàn)的內(nèi)存分配策略。
第五、六層
五六層就是具體的art內(nèi)存分配策略了,包括了BumpPointerSpace,ZygoteSpace,RegionSpace,RosAllocSpace,DlMallocSpace,它們都有自己特有的內(nèi)存分配策略,這些策略被應(yīng)用在不同的各個(gè)地方(主要由GC策略選擇不同的內(nèi)存分配策略)。下面我們按裝順序介紹一下這幾種內(nèi)存分配策略
內(nèi)存分配策略
BumpPointerSpace
BumpPointerSpace其實(shí)分配思想很簡(jiǎn)單,就是順序分配,比如我第一次分配分配10kb內(nèi)存,下一次再分配xxkb

BumpPointerSpace::BumpPointerSpace(const std::string& name, uint8_t* begin, uint8_t* limit)
: ContinuousMemMapAllocSpace(name,
MemMap::Invalid(),
begin,
begin,
limit,
kGcRetentionPolicyAlwaysCollect),
growth_end_(limit), 資源的尾部,
objects_allocated_(0), 當(dāng)前有多少個(gè)mirrorobject
bytes_allocated_(0), 分配的內(nèi)存大小
block_lock_("Block lock"),
main_block_size_(0) {
// This constructor gets called only from Heap::PreZygoteFork(), which
// doesn't require a mark_bitmap.
}
因?yàn)槭琼樞蚍峙?,因此我們只需記錄一個(gè)尾部end地址(已分配的最后一個(gè)地址尾部),就能在下次分配直接找到分配的開(kāi)始地址,非常簡(jiǎn)單,這里就不詳細(xì)說(shuō)分配過(guò)程,可以參考分配源碼
ZygoteSpace
ZygoteSpace本身沒(méi)有創(chuàng)建相關(guān)的內(nèi)存資源,而是通過(guò)外部傳入的MemMap對(duì)象,作為內(nèi)存資源,自身只是起到了一個(gè)管理作用
ZygoteSpace* ZygoteSpace::Create(const std::string& name,
MemMap&& mem_map,
accounting::ContinuousSpaceBitmap&& live_bitmap,
accounting::ContinuousSpaceBitmap&& mark_bitmap) {
DCHECK(live_bitmap.IsValid());
DCHECK(mark_bitmap.IsValid());
size_t objects_allocated = 0;
CountObjectsAllocated visitor(&objects_allocated);
ReaderMutexLock mu(Thread::Current(), *Locks::heap_bitmap_lock_);
live_bitmap.VisitMarkedRange(reinterpret_cast<uintptr_t>(mem_map.Begin()),
reinterpret_cast<uintptr_t>(mem_map.End()), visitor);
ZygoteSpace* zygote_space = new ZygoteSpace(name, std::move(mem_map), objects_allocated);
zygote_space->live_bitmap_ = std::move(live_bitmap);
zygote_space->mark_bitmap_ = std::move(mark_bitmap);
return zygote_space;
}
ZygoteSpace在art有特殊的含義,就是該內(nèi)存空間是繼承自Zygote進(jìn)程的資源存放地,雖然它繼承自AllocSpace,但是它的分配實(shí)現(xiàn)Alloc/Free,是不起作用的
mirror::Object* ZygoteSpace::Alloc(Thread*, size_t, size_t*, size_t*, size_t*) {
UNIMPLEMENTED(FATAL);
UNREACHABLE();
}
它作為一塊帶有“含義”的內(nèi)存資源而存在,同時(shí)它不同于ImageSpace,ImageSpace存放的是系統(tǒng)資源內(nèi)存,不會(huì)發(fā)生GC操作。而ZygoteSpace會(huì)在FullGC的時(shí)候,進(jìn)行GC的操作。因?yàn)閬?lái)自Zygote進(jìn)程的內(nèi)存,還是有回收的空間
RegionSpace
RegionSpace,其實(shí)是把內(nèi)存資源通過(guò)劃分的思想,把內(nèi)存劃分成一個(gè)個(gè)固定大小的Region而得名,相當(dāng)于內(nèi)存空間被劃分多個(gè)內(nèi)存塊,進(jìn)行內(nèi)存分配時(shí),在內(nèi)存塊分配即可。
RegionSpace::RegionSpace(const std::string& name, MemMap&& mem_map, bool use_generational_cc)
: ContinuousMemMapAllocSpace(name,
std::move(mem_map),
mem_map.Begin(),
mem_map.End(),
mem_map.End(),
kGcRetentionPolicyAlwaysCollect),
region_lock_("Region lock", kRegionSpaceRegionLock),
use_generational_cc_(use_generational_cc),
time_(1U),
num_regions_(mem_map_.Size() / kRegionSize),
madvise_time_(0U),
num_non_free_regions_(0U),
num_evac_regions_(0U),
max_peak_num_non_free_regions_(0U),
non_free_region_index_limit_(0U),
current_region_(&full_region_),
evac_region_(nullptr),
cyclic_alloc_region_index_(0U) {
CHECK_ALIGNED(mem_map_.Size(), kRegionSize);
CHECK_ALIGNED(mem_map_.Begin(), kRegionSize);
DCHECK_GT(num_regions_, 0U);
num_regions_ 個(gè)Region空間
regions_.reset(new Region[num_regions_]);
uint8_t* region_addr = mem_map_.Begin();
for (size_t i = 0; i < num_regions_; ++i, region_addr += kRegionSize) {
regions_[i].Init(i, region_addr, region_addr + kRegionSize);
}
mark_bitmap_ =
accounting::ContinuousSpaceBitmap::Create("region space live bitmap", Begin(), Capacity());
if (kIsDebugBuild) {
CHECK_EQ(regions_[0].Begin(), Begin());
for (size_t i = 0; i < num_regions_; ++i) {
CHECK(regions_[i].IsFree());
CHECK_EQ(static_cast<size_t>(regions_[i].End() - regions_[i].Begin()), kRegionSize);
if (i + 1 < num_regions_) {
CHECK_EQ(regions_[i].End(), regions_[i + 1].Begin());
}
}
CHECK_EQ(regions_[num_regions_ - 1].End(), Limit());
}
DCHECK(!full_region_.IsFree());
DCHECK(full_region_.IsAllocated());
size_t ignored;
DCHECK(full_region_.Alloc(kAlignment, &ignored, nullptr, &ignored) == nullptr);
// Protect the whole region space from the start.
Protect();
}
其中分配會(huì)采取內(nèi)存對(duì)其,由屬性kRegionSize決定(每個(gè)Region默認(rèn)1m)
因?yàn)槌橄蟪鰜?lái)一個(gè)個(gè)Region,我們能夠隨心的去分配想要的內(nèi)存,不局限內(nèi)存本身是否是連續(xù)的,同時(shí)因?yàn)槊總€(gè)內(nèi)存塊當(dāng)前的狀態(tài)不同(比如,有的已經(jīng)分配了,有的沒(méi)有),因此每個(gè)Region本身還對(duì)應(yīng)一個(gè)狀態(tài)RegionState
enum class RegionState : uint8_t {
kRegionStateFree, 該Region沒(méi)有內(nèi)存分配
kRegionStateAllocated, 內(nèi)存塊已分配
kRegionStateLarge, 大對(duì)象Region,比如4m的大對(duì)象(那么就需要4個(gè)Region),其中第一塊Region狀態(tài)為kRegionStateLarge,其他就是kRegionStateLargeTail
kRegionStateLargeTail, 如上
};
RegionSpace本身還定義著很多toSpace,fromSpace的操作,其實(shí)它就是Copying Collection(拷貝垃圾回收機(jī)制)的內(nèi)存分配模型。然后android默認(rèn)的垃圾回收機(jī)制,前臺(tái)是CMS,后臺(tái)是HSC,因此,RegionSpace能被發(fā)揮作用的地方其實(shí)還算是有點(diǎn)局限,除非你指定了Copying Collection垃圾回收機(jī)制
DlmallocSpace 與 RosAllocSpace
我們從上文的類圖可以看到DlmallocSpace與RosAllocSpace ,都是MallocSpace的子類,而我們的Heap里面,其實(shí)只有MallocSpace對(duì)象,具體選擇實(shí)現(xiàn)的子類是DlmallocSpace還是RosAllocSpace,其實(shí)由kUseRosAlloc決定,默認(rèn)是使用RosAllocSpace。
space::MallocSpace* Heap::CreateMallocSpaceFromMemMap(MemMap&& mem_map,
size_t initial_size,
size_t growth_limit,
size_t capacity,
const char* name,
bool can_move_objects) {
space::MallocSpace* malloc_space = nullptr;
if (kUseRosAlloc) {
// Create rosalloc space.
malloc_space = space::RosAllocSpace::CreateFromMemMap(std::move(mem_map),
name,
kDefaultStartingSize,
initial_size,
growth_limit,
capacity,
low_memory_mode_,
can_move_objects);
} else {
malloc_space = space::DlMallocSpace::CreateFromMemMap(std::move(mem_map),
name,
kDefaultStartingSize,
initial_size,
growth_limit,
capacity,
can_move_objects);
}
if (collector::SemiSpace::kUseRememberedSet) {
accounting::RememberedSet* rem_set =
new accounting::RememberedSet(std::string(name) + " remembered set", this, malloc_space);
CHECK(rem_set != nullptr) << "Failed to create main space remembered set";
AddRememberedSet(rem_set);
}
CHECK(malloc_space != nullptr) << "Failed to create " << name;
malloc_space->SetFootprintLimit(malloc_space->Capacity());
return malloc_space;
}
DlmallocSpace與RosAllocSpace兩者不同點(diǎn)就在于,實(shí)現(xiàn)的機(jī)制不一樣,DlmallocSpace采用的是dlmalloc內(nèi)存分配管理模型,它是一個(gè)開(kāi)源庫(kù),也是c語(yǔ)音malloc調(diào)用的具體實(shí)現(xiàn)。dlmalloc內(nèi)存機(jī)制如下(其實(shí)就是malloc的機(jī)制)

而RosAllocSpace,采用的是谷歌自己的內(nèi)存分配rosalloc完成,具體實(shí)現(xiàn)機(jī)制如下

rosalloc是一種動(dòng)態(tài)分配內(nèi)存算法,專門為了art虛擬機(jī)做了適配,其實(shí)它是一種多粒度內(nèi)存分配算法,ros的意思就是run of slot,可以理解為一個(gè)run是RosAllocSpace中內(nèi)存分配的單元,每個(gè)Run有自己的內(nèi)存分配粒度(slot)

比如Run1 中代表可分配10kb為單位的內(nèi)存,Run2則代表可分配1M為單位的內(nèi)存,具體的內(nèi)存由slot決定。
更加具體的分配細(xì)節(jié),可以在這里看到,我們就不深入細(xì)節(jié),有個(gè)概念就好。
LargeObjectSpace
它的創(chuàng)建過(guò)程很簡(jiǎn)單,就是初始化num_bytes_allocated_等分配的參數(shù)
LargeObjectSpace::LargeObjectSpace(const std::string& name, uint8_t* begin, uint8_t* end,
const char* lock_name)
: DiscontinuousSpace(name, kGcRetentionPolicyAlwaysCollect),
lock_(lock_name, kAllocSpaceLock),
num_bytes_allocated_(0), num_objects_allocated_(0), total_bytes_allocated_(0),
total_objects_allocated_(0), begin_(begin), end_(end) {
}
不知道大家有沒(méi)有發(fā)現(xiàn),LargeObjectSpace跟其他Space不一樣,它繼承于DiscontinuousSpace,意味著它的分配是不連續(xù)的,我們看一下它的分配過(guò)程就理解了
mirror::Object* LargeObjectMapSpace::Alloc(Thread* self, size_t num_bytes,
size_t* bytes_allocated, size_t* usable_size,
size_t* bytes_tl_bulk_allocated) {
std::string error_msg;
每次都調(diào)用MapAnonymous,其實(shí)它最終調(diào)用的就是mmap
MemMap mem_map = MemMap::MapAnonymous("large object space allocation",
num_bytes,
PROT_READ | PROT_WRITE,
/*low_4gb=*/ true,
&error_msg);
if (UNLIKELY(!mem_map.IsValid())) {
LOG(WARNING) << "Large object allocation failed: " << error_msg;
return nullptr;
}
mirror::Object* const obj = reinterpret_cast<mirror::Object*>(mem_map.Begin());
const size_t allocation_size = mem_map.BaseSize();
MutexLock mu(self, lock_);
large_objects_.Put(obj, LargeObject {std::move(mem_map), false /* not zygote */});
DCHECK(bytes_allocated != nullptr);
if (begin_ == nullptr || begin_ > reinterpret_cast<uint8_t*>(obj)) {
begin_ = reinterpret_cast<uint8_t*>(obj);
}
end_ = std::max(end_, reinterpret_cast<uint8_t*>(obj) + allocation_size);
*bytes_allocated = allocation_size;
if (usable_size != nullptr) {
*usable_size = allocation_size;
}
DCHECK(bytes_tl_bulk_allocated != nullptr);
*bytes_tl_bulk_allocated = allocation_size;
num_bytes_allocated_ += allocation_size;
total_bytes_allocated_ += allocation_size;
++num_objects_allocated_;
++total_objects_allocated_;
return obj;
}
可以看到,分配的時(shí)候,其實(shí)每次都是采用mmap去分配內(nèi)存空間,而LargeObjectMapSpace就是將mmap返回的空間進(jìn)行了一次管理,所以它才是不連續(xù)的,很好理解,這也意味指這塊內(nèi)存的增長(zhǎng),不會(huì)影響屬于連續(xù)內(nèi)存分配的其他Space(這里能做很多黑科技)。
那么我們分配的內(nèi)存什么時(shí)候會(huì)被劃分進(jìn)入LargeObjectSpace呢?其實(shí)我們直接看Heap分配條件即可,被定義為大對(duì)象要滿足以下條件
inline bool Heap::ShouldAllocLargeObject(ObjPtr<mirror::Class> c, size_t byte_count) const {
return byte_count >= large_object_threshold_ && (c->IsPrimitiveArray() || c->IsStringClass());
}
large_object_threshold_默認(rèn)為12kb
總結(jié)
我們通過(guò)本文,初步了解了art內(nèi)存分配的幾個(gè)模型,為什么要寫(xiě)這個(gè)系列呢,其實(shí)就是因?yàn)樽罱髲S很多針對(duì)內(nèi)存優(yōu)化的方案,都離不開(kāi)對(duì)虛擬機(jī)內(nèi)存分配的知識(shí)的了解!希望能夠通過(guò)這一系列的文章,能夠?qū)ψ约河袔椭?/p>
以上就是Art 虛擬機(jī)系列Heap內(nèi)存模型分配策略詳解的詳細(xì)內(nèi)容,更多關(guān)于Art 虛擬機(jī)Heap內(nèi)存分配的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android Studio做超好玩的拼圖游戲 附送詳細(xì)注釋源碼
這篇文章主要介紹了用Android Studio做的一個(gè)超好玩的拼圖游戲,你是0基礎(chǔ)Android小白也能包你學(xué)會(huì),另外附送超詳細(xì)注釋的源碼,建議收藏!2021-08-08
Android性能優(yōu)化之ANR問(wèn)題定位分析
這篇文章主要介紹了Android性能優(yōu)化之ANR問(wèn)題定位分析,ANR應(yīng)用程序未響應(yīng),當(dāng)主線程被阻塞時(shí),就會(huì)彈出如下彈窗,文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可任意參考一下2022-08-08
Android中AlarmManager+Notification實(shí)現(xiàn)定時(shí)通知提醒功能
本篇文章主要介紹了Android中AlarmManager+Notification實(shí)現(xiàn)定時(shí)通知提醒功能,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2017-10-10
仿餓了嗎點(diǎn)餐界面ListView聯(lián)動(dòng)的實(shí)現(xiàn)
這篇文章主要介紹了仿餓了嗎點(diǎn)餐界面ListView聯(lián)動(dòng)的實(shí)現(xiàn)的相關(guān)資料,本文介紹的非常詳細(xì),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-09-09
flutter實(shí)現(xiàn)掃碼槍獲取數(shù)據(jù)源禁止系統(tǒng)鍵盤彈窗示例詳解
這篇文章主要為大家介紹了flutter實(shí)現(xiàn)掃碼槍獲取數(shù)據(jù)源禁止系統(tǒng)鍵盤彈窗示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01

