Android Runtime内存碎片化处理策略原理深度剖析

一、内存碎片化问题概述

在Android Runtime(ART)的内存管理体系中,内存碎片化是影响内存使用效率和应用性能的关键问题。随着应用程序不断进行内存分配与释放操作,原本连续的内存空间会被分割成大量不连续的小块,导致后续大内存分配请求难以满足,即使总空闲内存足够,也会因缺乏连续空间而失败。内存碎片化分为外部碎片化内部碎片化:外部碎片化指空闲内存总量充足,但分散为小块无法满足大内存请求;内部碎片化则是已分配内存中存在未使用的部分。ART通过多种策略协同处理这两类碎片化问题,其核心逻辑分布在art/runtime/gc/art/runtime/memory/等目录的源码中。

二、内存碎片化检测机制

2.1 碎片化程度评估指标

ART通过计算多个指标量化内存碎片化程度,主要在art/runtime/gc/heap.cc中实现。核心指标包括:

  • 空闲内存块平均大小:计算所有空闲内存块的平均尺寸,若该值过小,说明碎片化严重。
// Heap类中计算空闲内存块平均大小的方法
size_t Heap::CalculateAverageFreeBlockSize() const {size_t total_free_size = 0;size_t num_free_blocks = 0;// 遍历所有空闲链表(不同大小类别的空闲内存块链表)for (size_t i = 0; i < kNumSizeClasses; ++i) {FreeList* free_list = free_lists_[i];FreeBlock* current = free_list->GetHead();while (current != nullptr) {total_free_size += current->size_;num_free_blocks++;current = current->next_;}}// 避免除零错误return (num_free_blocks > 0)? total_free_size / num_free_blocks : 0; 
}
  • 空闲内存块数量与分布:统计不同大小的空闲块数量,分析其分布情况。若小尺寸空闲块过多,表明碎片化加剧。
  • 碎片化率:通过公式 (总空闲内存 - 最大连续空闲内存) / 总空闲内存 计算,直观反映内存碎片化比例。

2.2 动态监测与触发机制

ART采用定期检测与事件触发相结合的方式监测碎片化。在art/runtime/gc/gc_controller.cc中,GcController类负责管理GC触发逻辑:

class GcController {
public:void MonitorFragmentation() {// 定期检查(如每5秒一次)std::thread([this]() {while (true) {std::this_thread::sleep_for(std::chrono::seconds(5));size_t avg_free_size = heap_->CalculateAverageFreeBlockSize();// 若平均空闲块大小低于阈值,触发碎片化处理if (avg_free_size < kFragmentationThreshold * heap_->GetHeapSize()) {TriggerDefragmentation(); }}}).detach();}void TriggerDefragmentation() {// 选择合适的碎片化处理策略(如标记-压缩GC)GcCollector* collector = SelectDefragmentationCollector(); collector->Collect();}
private:static const double kFragmentationThreshold = 0.05; // 阈值设定为总堆内存的5%Heap* heap_;
};

此外,在内存分配失败时,也会立即检测碎片化并尝试处理,避免因碎片化导致的分配失败问题持续恶化。

三、基于垃圾回收的碎片化处理策略

3.1 标记-压缩(Mark-Compact)算法

标记-压缩算法是ART处理碎片化的核心手段,在art/runtime/gc/collector/mark_compact_collector.cc中实现。其执行流程如下:

  1. 标记阶段:暂停所有应用线程(Stop The World),从根集合(如栈、静态变量)开始,标记所有可达对象。
class MarkCompactCollector {
private:void MarkPhase() {Thread::SuspendAll();RootScanner scanner;MarkClosure mark_closure(this);scanner.ScanRoots(&mark_closure);// 扫描堆内存,标记可达对象ScanHeapForLiveObjects(&mark_closure); Thread::ResumeAll();}void ScanHeapForLiveObjects(Closure* closure) {uint8_t* current = heap_->GetHeapStart();uint8_t* end = heap_->GetHeapEnd();while (current < end) {ObjectHeader* header = reinterpret_cast<ObjectHeader*>(current);size_t size = header->size_;if (!header->IsMarked()) {// 未标记对象为垃圾,跳过current += size;continue;}// 调用闭包处理可达对象closure->DoObject(reinterpret_cast<void*>(header)); current += size;}}
};
  1. 压缩阶段:将所有存活对象移动到内存一端,紧凑排列,同时回收空闲内存。
class MarkCompactCollector {
private:void CompactPhase() {Thread::SuspendAll();uint8_t* target = heap_->GetHeapStart();uint8_t* current = heap_->GetHeapStart();while (current < heap_->GetHeapEnd()) {ObjectHeader* header = reinterpret_cast<ObjectHeader*>(current);size_t size = header->size_;if (header->IsMarked()) {// 复制存活对象到目标位置memcpy(target, current, size); // 更新对象引用(指向新地址)UpdateObjectReferences(current, target); target += size;}current += size;}// 更新堆内存边界heap_->SetHeapEnd(target); Thread::ResumeAll();}
};

通过标记-压缩,ART可有效消除外部碎片化,提供连续的空闲内存空间。

3.2 分代回收策略优化

ART采用分代回收架构,针对不同生命周期的对象采用不同策略。新生代(Young Generation)使用复制算法快速回收短命对象,避免碎片化;老年代(Old Generation)则通过标记-压缩处理碎片化。在art/runtime/gc/gc_policy.cc中定义分代策略:

class GcPolicy {
public:GcType SelectGcTypeForGeneration(Generation generation) {if (generation == kYoungGeneration) {return kCopying; // 新生代用复制算法} else {return kMarkCompact; // 老年代用标记-压缩算法}}
};

分代策略减少了不必要的内存移动,提高了碎片化处理效率。例如,新生代对象存活率低,复制算法能快速回收空间且无碎片;老年代对象存活率高,标记-压缩更适合处理碎片化。

四、内存分配器优化策略

4.1 伙伴系统(Buddy System)

伙伴系统是ART处理大块内存分配的重要手段,在art/runtime/gc/allocator/buddy_allocator.cc中实现。其核心思想是将内存按2的幂次方大小划分,通过合并和分割操作减少碎片化。

class BuddyAllocator {
private:// 内存块链表,每个链表存储特定大小的空闲块BuddyBlock* free_lists_[kMaxBuddyLevels]; // 计算满足需求的最小2的幂次方内存块级别int CalculateBuddyLevel(size_t size) {int level = 0;size_t block_size = 1;while (block_size < size) {block_size <<= 1;level++;}return level;}// 分配内存块void* Allocate(size_t size) {int level = CalculateBuddyLevel(size);for (int i = level; i < kMaxBuddyLevels; ++i) {if (free_lists_[i] != nullptr) {BuddyBlock* block = free_lists_[i];RemoveFromList(block, i);if (i > level) {// 分割大块内存SplitBlock(block, i); return Allocate(size);}return block->data;}}return nullptr; // 分配失败}// 回收内存块void Free(void* block) {BuddyBlock* buddy_block = reinterpret_cast<BuddyBlock*>(block - offsetof(BuddyBlock, data));int level = buddy_block->level;BuddyBlock* buddy = GetBuddy(buddy_block);if (buddy != nullptr && buddy->IsFree()) {// 合并相邻的伙伴块MergeBlocks(buddy_block, buddy); Free(buddy_block);return;}AddToList(buddy_block, level); // 无法合并则加入链表}
};

伙伴系统通过高效的分割与合并,确保大块内存分配时减少碎片化,提高内存利用率。

4.2 空闲链表管理优化

ART使用空闲链表(Free List)管理不同大小的空闲内存块,在art/runtime/gc/allocator/free_list.cc中实现。为减少碎片化,采用以下策略:

  • 按大小分类:将空闲块按尺寸分为多个链表,分配时优先从合适大小的链表中查找,避免大材小用。
class FreeList {
public:FreeList(size_t size_class) : size_class_(size_class) {}FreeBlock* GetHead() const { return head_; }// 插入空闲块到链表void InsertBlock(FreeBlock* block) {block->next_ = head_;if (head_ != nullptr) {head_->prev_ = block;}head_ = block;}// 从链表中移除并分配空闲块FreeBlock* AllocateBlock(size_t size) {FreeBlock* current = head_;while (current != nullptr) {if (current->size_ >= size) {RemoveFromList(current);return current;}current = current->next_;}return nullptr;}
private:FreeBlock* head_;size_t size_class_;
};
  • 合并相邻空闲块:在内存释放时,检查相邻块是否空闲,若空闲则合并,减少小碎片数量。
class FreeList {
private:void MergeAdjacentBlocks() {FreeBlock* current = head_;while (current != nullptr) {FreeBlock* next = current->next_;if (next != nullptr && IsAdjacent(current, next)) {current->size_ += next->size_;RemoveFromList(next);}current = current->next_;}}bool IsAdjacent(FreeBlock* block1, FreeBlock* block2) {return reinterpret_cast<uint8_t*>(block1->data) + block1->size_ == reinterpret_cast<uint8_t*>(block2->data);}
};

五、内存整理(Defragmentation)后台任务

5.1 定期内存整理机制

为避免碎片化问题累积,ART启动后台线程定期执行内存整理任务,在art/runtime/gc/background_defragmentation.cc中实现:

class BackgroundDefragmentationTask : public Task {
public:BackgroundDefragmentationTask(Heap* heap) : heap_(heap) {}void Run() override {while (true) {std::this_thread::sleep_for(std::chrono::minutes(10)); // 每10分钟检查一次size_t avg_free_size = heap_->CalculateAverageFreeBlockSize();if (avg_free_size < kFragmentationThreshold * heap_->GetHeapSize()) {// 碎片化严重,执行标记-压缩GCGcCollector* collector = new MarkCompactCollector(heap_); collector->Collect();delete collector;}}}
private:Heap* heap_;static const double kFragmentationThreshold = 0.05;
};

后台任务在系统负载较低时执行,减少对应用性能的影响。

5.2 增量式内存整理

为进一步降低停顿时间,ART支持增量式内存整理,将整理过程拆分为多个小阶段。在art/runtime/gc/collector/incremental_mark_compact.cc中实现:

class IncrementalMarkCompactCollector {
public:void Collect() {// 初始标记阶段(STW)InitialMarkPhase(); // 多次增量标记阶段for (int i = 0; i < kNumIncrementalSteps; ++i) {IncrementalMarkPhase();Thread::ResumeAll();std::this_thread::sleep_for(std::chrono::milliseconds(kIncrementalPause));Thread::SuspendAll();}// 最终标记阶段(STW)FinalMarkPhase(); // 增量压缩阶段for (int i = 0; i < kNumIncrementalSteps; ++i) {IncrementalCompactPhase();Thread::ResumeAll();std::this_thread::sleep_for(std::chrono::milliseconds(kIncrementalPause));Thread::SuspendAll();}Thread::ResumeAll();}
private:// 各阶段具体实现...
};

增量式整理通过分阶段执行和写屏障(Write Barrier)记录对象引用变化,在减少停顿时间的同时保证内存整理的正确性。

六、内存压缩(Memory Compression)技术

6.1 压缩原理与实现

当物理内存不足且碎片化严重时,ART利用内存压缩技术缓解压力。在art/runtime/memory/memory_compression.cc中实现:

class MemoryCompressor {
public:void CompressMemory() {// 检查内存压力(如空闲内存低于阈值)if (IsMemoryPressureHigh()) {PageTable* page_table = GetCurrentProcessPageTable();for (size_t i = 0; i < kNumPageTableEntries; ++i) {PageTableEntry* entry = &page_table->entries_[i];if (entry->is_present &&!entry->is_dirty && entry->last_access_time < kLongTimeThreshold) {void* physical_page = entry->physical_address;// 压缩页面数据CompressPage(physical_page); entry->is_present = false;entry->compressed_data = GetCompressedData(physical_page);}}}}
private:bool IsMemoryPressureHigh() const {// 获取系统空闲内存并与阈值比较size_t free_memory = GetFreePhysicalMemory();return free_memory < kLowMemoryThreshold; }void CompressPage(void* physical_page) {// 调用压缩算法(如zlib库)char* compressed_data = CompressData(reinterpret_cast<char*>(physical_page), kPageSize); // 存储压缩后的数据StoreCompressedData(compressed_data); }
};

内存压缩将不常用页面数据压缩存储,释放物理内存,减少因碎片化导致的内存分配失败。

6.2 解压缩与数据恢复

当再次访问压缩页面时,需进行解压缩:

class MemoryCompressor {
public:void* DecompressPage(void* compressed_data) {// 分配新的物理页面void* new_physical_page = AllocatePhysicalPage(); if (new_physical_page != nullptr) {char* decompressed_data = DecompressData(compressed_data);memcpy(new_physical_page, decompressed_data, kPageSize);free(decompressed_data);}return new_physical_page;}
};

内存压缩技术在不释放内存的情况下提升了内存使用效率,是应对碎片化和内存压力的有效补充手段。

七、内存分配策略动态调整

7.1 基于负载的分配策略切换

ART根据应用负载动态调整内存分配策略。在art/runtime/gc/allocator/allocator_policy.cc中实现:

class AllocatorPolicy {
public:Allocator* SelectAllocator() {// 统计最近一段时间的内存分配模式AllocationPattern pattern = AnalyzeAllocationPattern(); if (pattern == kLargeObjectDominant) {return new BuddyAllocator(); // 适合大块内存分配} else if (pattern == kSmallObjectFrequent) {return new FreeListAllocator(); // 适合小对象频繁分配}return nullptr;}
private:AllocationPattern AnalyzeAllocationPattern() {// 统计不同大小内存分配的频率和占比// 根据阈值判断分配模式}
};

通过动态切换分配策略,减少因策略不当导致的碎片化问题。

7.2 自适应内存分配阈值

ART还会根据内存使用情况调整分配阈值。例如,在内存紧张时缩小TLAB(Thread-Local Allocation Buffer)大小,避免过度分配导致碎片化:

class Heap {
public:void AdjustTlabSize() {size_t free_memory = GetFreeMemory();if (free_memory < kLowMemoryThreshold) {