Android Runtime内存碎片化处理策略原理(56)

155 阅读9分钟

码字不易,请大佬们点点关注,谢谢~

一、内存碎片化问题概述

在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()) {
                // 碎片化严重,执行标记-压缩GC
                GcCollector* 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) {