Android Runtime大对象分配与处理流程原理深度剖析(59)

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

一、大对象定义与分类标准

在Android Runtime(ART)中,大对象的定义与系统架构和内存管理策略密切相关。根据源码分析,大对象的分类标准主要基于对象大小阈值,该阈值在不同Android版本中可能有所调整。在art/runtime/gc/heap.h文件中,定义了大对象的判断条件:

cpp 复制代码
// 大对象阈值定义(示例值,实际可能因版本而异)
static constexpr size_t kLargeObjectThreshold = 128 * KB; // 128KB

bool Heap::IsLargeObject(size_t size) const {
    return size >= kLargeObjectThreshold;
}

这个阈值并非固定不变,而是根据设备内存状况和运行时配置动态调整。例如,在内存紧张的设备上,阈值可能会降低以减少大对象对内存的压力。

1.1 大对象的类型分布

大对象在Android应用中主要包括以下几类:

  1. 大型数组:如图片解码后的Bitmap数据、音频/视频缓冲区等
  2. 长字符串:如大型JSON数据、HTML文档等
  3. 大型集合对象:如包含大量元素的ArrayList、HashMap等
  4. 自定义大型对象:某些应用特有的大型数据结构

这些大对象在内存中占用连续空间,对内存分配和垃圾回收机制提出了特殊挑战。

1.2 大对象对内存管理的挑战

大对象的存在对内存管理系统带来了多方面的挑战:

  1. 外部碎片化:大对象的分配和释放容易导致内存空间碎片化,使得后续即使有足够的总内存,也无法分配连续的大块内存
  2. GC压力:大对象的复制和移动操作成本高昂,会显著增加垃圾回收的停顿时间
  3. 分配失败风险:由于需要连续内存空间,大对象分配失败的概率远高于小对象

为应对这些挑战,ART采用了专门的大对象管理策略,下面将详细分析。

二、大对象分配流程详解

大对象的分配流程与普通对象有显著差异,主要体现在分配路径、内存来源和错误处理等方面。

2.1 分配路径选择

当应用请求分配内存时,ART首先判断是否为大对象,然后选择不同的分配路径。在art/runtime/gc/heap.cc中:

cpp 复制代码
void* Heap::AllocateInternalWithGc(Thread* self, size_t size, size_t* bytes_allocated,
                                   GcAllocatorType allocator_type, bool instrumented) {
    // 检查是否为大对象
    if (IsLargeObject(size)) {
        // 走大对象分配路径
        return AllocateLargeObject(self, size, bytes_allocated, allocator_type, instrumented);
    } else {
        // 走普通对象分配路径
        return AllocateSmallObject(self, size, bytes_allocated, allocator_type, instrumented);
    }
}

2.2 大对象专用分配器

ART为大对象设计了专用的分配器,避免与小对象共享内存池。在art/runtime/gc/allocator/large_object_allocator.cc中:

cpp 复制代码
class LargeObjectAllocator {
public:
    explicit LargeObjectAllocator(Heap* heap) : heap_(heap) {}
    
    // 大对象分配函数
    mirror::Object* Allocate(Thread* self, size_t size, size_t* bytes_allocated) {
        // 检查是否有足够的内存
        if (!heap_->IsEnoughMemoryAvailable(size)) {
            // 内存不足,触发GC
            heap_->CollectGarbageInternal(GcCause::kGcCauseLargeObjectAllocation, true);
            // 再次检查
            if (!heap_->IsEnoughMemoryAvailable(size)) {
                return nullptr; // 内存仍然不足,分配失败
            }
        }
        
        // 分配内存(直接从操作系统获取)
        void* memory = AllocateFromOs(size);
        if (memory == nullptr) {
            return nullptr; // 操作系统分配失败
        }
        
        // 初始化对象头
        mirror::Object* obj = InitializeObjectHeader(memory, size);
        
        // 记录大对象信息
        heap_->RecordLargeObject(obj, size);
        
        if (bytes_allocated != nullptr) {
            *bytes_allocated = size;
        }
        
        return obj;
    }
    
private:
    Heap* heap_;
    
    // 从操作系统直接分配内存
    void* AllocateFromOs(size_t size) {
        // 使用mmap系统调用分配内存
        void* ptr = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
        if (ptr == MAP_FAILED) {
            PLOG(ERROR) << "Failed to mmap large object of size " << size;
            return nullptr;
        }
        return ptr;
    }
    
    // 初始化对象头信息
    mirror::Object* InitializeObjectHeader(void* memory, size_t size) {
        mirror::Object* obj = reinterpret_cast<mirror::Object*>(memory);
        // 设置对象类信息
        obj->SetClass(heap_->GetClassRoot(kJavaLangObject));
        // 设置对象大小
        obj->SetSize(size);
        // 初始化其他对象头字段
        // ...
        return obj;
    }
};

2.3 内存对齐与填充

大对象分配时需要考虑内存对齐问题,以确保高效访问。在art/runtime/gc/allocator/large_object_allocator.cc中:

cpp 复制代码
size_t LargeObjectAllocator::AlignObjectSize(size_t size) const {
    // 确保对象大小是对象对齐单位的倍数
    return RoundUp(size, kObjectAlignment);
}

// 计算实际分配的内存大小(包含填充)
size_t LargeObjectAllocator::ComputeAllocationSize(size_t size) const {
    // 对象头大小
    size_t header_size = mirror::Object::SizeOf();
    // 实际数据大小
    size_t data_size = size - header_size;
    // 对齐后的数据大小
    size_t aligned_data_size = AlignObjectSize(data_size);
    // 总分配大小
    return header_size + aligned_data_size;
}

这种对齐策略确保了大对象在内存中的高效存储和访问,但也可能导致少量内存浪费(填充区域)。

三、大对象内存管理策略

3.1 独立内存区域

ART为大对象分配独立的内存区域,与新生代和老年代分离。在art/runtime/gc/heap.h中:

cpp 复制代码
class Heap {
private:
    // 大对象空间
    Space* large_object_space_;
    
    // 初始化大对象空间
    void InitLargeObjectSpace() {
        // 创建大对象专用空间
        large_object_space_ = CreateLargeObjectSpace();
        // 注册空间
        AddSpace(large_object_space_);
    }
    
    // 创建大对象空间
    Space* CreateLargeObjectSpace() {
        // 根据平台特性选择合适的空间实现
        if (kUseLargeObjectSpaceArena) {
            return new LargeObjectSpaceArena();
        } else {
            return new LargeObjectSpaceMmap();
        }
    }
};

这种分离策略有以下优点:

  • 避免大对象的分配和回收影响新生代和老年代的内存布局
  • 简化了大对象的垃圾回收过程
  • 减少了碎片化对普通对象分配的影响

3.2 大对象空间实现

大对象空间有多种实现方式,其中最常见的是基于mmap的实现。在art/runtime/gc/space/large_object_space_mmap.cc中:

cpp 复制代码
class LargeObjectSpaceMmap : public Space {
public:
    LargeObjectSpaceMmap(const std::string& name) : Space(name, kSpaceTypeLargeObjectSpaceMmap) {}
    
    // 分配大对象内存
    mirror::Object* Allocate(size_t size, size_t* bytes_allocated) override {
        // 使用mmap分配内存
        void* memory = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
        if (memory == MAP_FAILED) {
            PLOG(ERROR) << "Failed to mmap large object of size " << size;
            return nullptr;
        }
        
        // 记录分配的内存块
        large_objects_.push_back({memory, size});
        
        if (bytes_allocated != nullptr) {
            *bytes_allocated = size;
        }
        
        return reinterpret_cast<mirror::Object*>(memory);
    }
    
    // 释放大对象内存
    void Free(mirror::Object* obj) override {
        size_t size = obj->SizeOf();
        void* memory = obj;
        
        // 查找并移除内存块记录
        for (auto it = large_objects_.begin(); it != large_objects_.end(); ++it) {
            if (it->memory == memory && it->size == size) {
                large_objects_.erase(it);
                break;
            }
        }
        
        // 使用munmap释放内存
        if (munmap(memory, size) != 0) {
            PLOG(ERROR) << "Failed to munmap large object of size " << size;
        }
    }
    
private:
    // 记录所有分配的大对象
    struct LargeObject {
        void* memory;
        size_t size;
    };
    std::vector<LargeObject> large_objects_;
};

3.3 内存预分配与保留

为提高大对象分配效率,ART支持内存预分配和保留机制。在art/runtime/gc/heap.cc中:

cpp 复制代码
void Heap::PreallocateLargeObjectSpace(size_t size) {
    // 预分配指定大小的大对象空间
    if (large_object_space_->Preallocate(size)) {
        LOG(INFO) << "Preallocated " << PrettySize(size) << " for large objects";
    } else {
        LOG(WARNING) << "Failed to preallocate large object space of size " << PrettySize(size);
    }
}

bool Heap::ReserveLargeObjectSpace(size_t size) {
    // 保留大对象空间,防止被其他分配占用
    return large_object_space_->Reserve(size);
}

这些机制在应用启动阶段或已知需要大量大对象分配时特别有用,可以减少运行时的分配延迟。

四、大对象垃圾回收机制

4.1 大对象的标记过程

大对象的垃圾回收过程与普通对象有所不同。在标记阶段,GC需要特殊处理大对象。在art/runtime/gc/collector/mark_sweep_collector.cc中:

cpp 复制代码
void MarkSweepCollector::MarkPhase() {
    // 标记根集合
    MarkRoots();
    
    // 标记大对象空间中的对象
    MarkLargeObjects();
    
    // 标记其他空间中的对象
    MarkHeap();
}

void MarkSweepCollector::MarkLargeObjects() {
    Space* los = heap_->GetLargeObjectSpace();
    if (los == nullptr) {
        return;
    }
    
    // 遍历大对象空间中的所有对象
    for (mirror::Object* obj : los->GetObjects()) {
        if (!IsMarked(obj)) {
            // 递归标记对象及其引用
            MarkObject(obj);
        }
    }
}

4.2 大对象的回收策略

大对象的回收策略主要有两种:立即回收和延迟回收。在art/runtime/gc/collector/mark_sweep_collector.cc中:

cpp 复制代码
void MarkSweepCollector::SweepPhase() {
    // 回收普通对象空间
    SweepSmallObjects();
    
    // 回收大对象空间
    SweepLargeObjects();
}

void MarkSweepCollector::SweepLargeObjects() {
    Space* los = heap_->GetLargeObjectSpace();
    if (los == nullptr) {
        return;
    }
    
    // 遍历大对象空间中的所有对象
    std::vector<mirror::Object*> objects_to_free;
    for (mirror::Object* obj : los->GetObjects()) {
        if (!IsMarked(obj)) {
            // 对象未被标记,加入待回收列表
            objects_to_free.push_back(obj);
        } else {
            // 清除标记位,为下一次GC做准备
            ClearMark(obj);
        }
    }
    
    // 回收未标记的大对象
    for (mirror::Object* obj : objects_to_free) {
        los->Free(obj);
    }
}

4.3 大对象的晋升策略

大对象通常不会经历从新生代到老年代的晋升过程,而是直接分配在特殊的大对象空间。但在某些情况下,大对象可能会被移动到其他空间。在art/runtime/gc/heap.cc中:

cpp 复制代码
bool Heap::ShouldPromoteLargeObject(mirror::Object* obj, size_t size) {
    // 根据对象年龄和大小决定是否晋升
    uint8_t age = obj->GetAge();
    if (age >= kMaxLargeObjectAge) {
        // 对象年龄超过阈值,晋升到老年代
        return true;
    }
    
    // 检查大对象空间的使用情况
    if (large_object_space_->GetOccupancyRatio() > kLargeObjectSpaceHighWatermark) {
        // 大对象空间使用率过高,考虑晋升
        return true;
    }
    
    return false;
}

大对象的晋升机制有助于平衡不同内存区域的使用压力,提高整体内存管理效率。

五、大对象与内存碎片化

5.1 大对象对内存碎片化的影响

大对象的分配和回收容易导致内存碎片化,尤其是外部碎片化。由于大对象需要连续的内存空间,当它们被释放后,可能会留下无法被充分利用的"空洞"。在art/runtime/gc/heap.cc中:

cpp 复制代码
size_t Heap::CalculateExternalFragmentation() const {
    // 获取大对象空间的空闲内存块信息
    std::vector<FreeBlock> free_blocks = large_object_space_->GetFreeBlocks();
    
    // 计算最大连续空闲内存
    size_t max_continuous_free = 0;
    for (const FreeBlock& block : free_blocks) {
        if (block.size > max_continuous_free) {
            max_continuous_free = block.size;
        }
    }
    
    // 计算总空闲内存
    size_t total_free = large_object_space_->GetFreeMemory();
    
    // 计算外部碎片化率
    if (total_free == 0) {
        return 0;
    }
    return 100 * (total_free - max_continuous_free) / total_free;
}

5.2 缓解大对象导致的碎片化

为缓解大对象导致的碎片化问题,ART采用了多种策略:

  1. 大对象合并 :在释放大对象时,尝试合并相邻的空闲块。在art/runtime/gc/space/large_object_space_mmap.cc中:
cpp 复制代码
void LargeObjectSpaceMmap::Free(mirror::Object* obj) {
    size_t size = obj->SizeOf();
    void* memory = obj;
    
    // 释放内存前,尝试合并相邻的空闲块
    MergeAdjacentFreeBlocks(memory, size);
    
    // 执行正常的内存释放
    // ...
}

void LargeObjectSpaceMmap::MergeAdjacentFreeBlocks(void* memory, size_t size) {
    // 查找相邻的空闲块
    for (auto it = free_blocks_.begin(); it != free_blocks_.end(); ++it) {
        // 检查是否可以合并
        if (AreAdjacent(memory, size, it->memory, it->size)) {
            // 合并两个空闲块
            void* new_memory = std::min(memory, it->memory);
            size_t new_size = size + it->size;
            
            // 移除原空闲块
            free_blocks_.erase(it);
            
            // 插入合并后的空闲块
            InsertFreeBlock(new_memory, new_size);
            return;
        }
    }
    
    // 没有相邻空闲块,直接插入新的空闲块
    InsertFreeBlock(memory, size);
}
  1. 内存整理:定期执行内存整理,将存活的大对象移动到一起,形成更大的连续空闲空间。

  2. 大对象预分配:通过预分配大对象空间,减少运行时的碎片化风险。

六、大对象分配失败处理机制

6.1 分配失败检测

当大对象分配失败时,ART会触发一系列处理机制。在art/runtime/gc/heap.cc中:

cpp 复制代码
void* Heap::AllocateInternalWithGc(Thread* self, size_t size, size_t* bytes_allocated,
                                   GcAllocatorType allocator_type, bool instrumented) {
    // 尝试分配内存
    void* result = AllocateInternalWithoutGc(self, size, bytes_allocated, allocator_type);
    
    if (result == nullptr) {
        // 分配失败,记录OOM信息
        RecordOOMEvent(self, size, allocator_type);
        
        // 尝试GC后再次分配
        bool gc_locked = TryGc(self, size, allocator_type);
        if (gc_locked) {
            result = AllocateInternalWithoutGc(self, size, bytes_allocated, allocator_type);
            ReleaseGcLock(self);
        }
    }
    
    return result;
}

6.2 内存不足处理流程

当检测到内存不足时,ART会执行以下处理流程:

  1. 触发垃圾回收:尝试回收未使用的内存。

  2. 内存压缩:执行内存压缩,减少碎片化。

  3. 杀死后台进程:在极端情况下,杀死后台进程以释放更多内存。

art/runtime/gc/heap.cc中:

cpp 复制代码
bool Heap::TryGc(Thread* self, size_t size, GcAllocatorType allocator_type) {
    // 判断是否需要触发GC
    GcCause cause = DetermineGcCause(size, allocator_type);
    
    // 触发GC
    CollectGarbageInternal(cause, /* is_explicit */ false);
    
    // 检查GC后是否有足够内存
    return IsEnoughMemoryAvailable(size);
}

void Heap::CollectGarbageInternal(GcCause cause, bool is_explicit) {
    // 选择合适的GC收集器
    GcCollector* collector = SelectGcCollector(cause);
    
    // 执行GC
    collector->Run(cause, is_explicit);
}

6.3 OOM异常处理

如果所有尝试都失败,ART会抛出OutOfMemoryError异常。在art/runtime/thread.cc中:

cpp 复制代码
void Thread::ThrowOutOfMemoryError(const char* msg) {
    ScopedObjectAccess soa(this);
    // 创建OutOfMemoryError异常对象
    mirror::Throwable* exception = soa.Decode<mirror::Throwable*>(
        Runtime::Current()->ThrowNewException("Ljava/lang/OutOfMemoryError;", msg));
    
    // 记录OOM堆栈信息
    RecordOomStacktrace(exception);
    
    // 触发OOM回调
    Runtime::Current()->GetHeap()->OnOom(exception);
}

七、大对象监控与性能调优

7.1 大对象监控工具

ART提供了多种工具用于监控大对象的分配和使用情况:

  1. 内存分析工具:如Android Profiler,可以可视化大对象的分配和回收情况。

  2. GC日志:记录大对象相关的GC事件,帮助分析性能问题。

  3. 堆转储(Heap Dump):捕获应用内存快照,分析大对象的分布和引用关系。

art/runtime/gc/heap.cc中,实现了大对象监控的核心逻辑:

cpp 复制代码
void Heap::DumpLargeObjects(std::ostream& os) const {
    Space* los = large_object_space_;
    if (los == nullptr) {
        os << "No large object space found." << std::endl;
        return;
    }
    
    os << "Large Objects (" << los->NumObjects() << " objects):" << std::endl;
    
    // 遍历大对象空间中的所有对象
    size_t total_size = 0;
    for (mirror::Object* obj : los->GetObjects()) {
        size_t size = obj->SizeOf();
        total_size += size;
        
        // 获取对象类名
        mirror::Class* klass = obj->GetClass();
        std::string class_name = klass != nullptr ? klass->PrettyName() : "unknown";
        
        // 输出对象信息
        os << "  " << class_name << " @ " << obj
           << " size=" << PrettySize(size) << std::endl;
    }
    
    os << "Total large object memory: " << PrettySize(total_size) << std::endl;
}

7.2 性能调优建议

基于大对象的特性和ART的实现原理,以下是一些性能调优建议:

  1. 避免创建过大对象:尽量将大对象拆分为多个小对象,减少内存压力。

  2. 重用大对象:对于需要频繁使用的大对象(如缓冲区),考虑实现对象池进行重用。

  3. 优化大对象生命周期:及时释放不再使用的大对象,避免长时间占用内存。

  4. 监控大对象分配模式:通过分析大对象分配模式,提前预分配或调整分配策略。

  5. 调整大对象阈值:根据应用特性,适当调整大对象阈值,平衡分配效率和碎片化。

八、大对象与内存压缩

8.1 内存压缩对大对象的处理

内存压缩是处理碎片化的有效手段,但对大对象的处理需要特殊考虑。在art/runtime/gc/collector/mark_compact_collector.cc中:

cpp 复制代码
void MarkCompactCollector::CompactPhase() {
    // 计算对象的新位置
    ComputeNewObjectAddresses();
    
    // 更新对象引用
    UpdateObjectReferences();
    
    // 移动对象(包括大对象)
    MoveObjects();
}

void MarkCompactCollector::MoveObjects() {
    // 先移动普通对象
    MoveSmallObjects();
    
    // 再移动大对象
    MoveLargeObjects();
}

void MarkCompactCollector::MoveLargeObjects() {
    Space* los = heap_->GetLargeObjectSpace();
    if (los == nullptr) {
        return;
    }
    
    // 遍历大对象空间中的所有对象
    for (mirror::Object* obj : los->GetObjects()) {
        if (IsMarked(obj)) {
            // 获取对象的新位置
            void* new_addr = GetNewObjectAddress(obj);
            
            // 移动对象
            memmove(new_addr, obj, obj->SizeOf());
            
            // 更新对象头中的地址信息
            UpdateObjectHeader(obj, new_addr);
        }
    }
}

8.2 大对象压缩的性能考量

大对象的压缩操作成本较高,主要体现在以下几个方面:

  1. 内存带宽消耗:移动大对象需要大量内存带宽,可能影响应用性能。

  2. STW时间增加:大对象压缩通常需要在STW(Stop The World)阶段进行,延长了应用暂停时间。

  3. 缓存失效:大对象移动后,CPU缓存失效,可能导致后续访问性能下降。

为平衡性能和内存碎片化,ART在执行内存压缩时会考虑大对象的特殊性质,选择性地对部分大对象进行压缩。

九、大对象分配的安全机制

9.1 内存访问保护

ART为大对象分配的内存设置了访问保护机制,防止越界访问。在art/runtime/gc/space/large_object_space_mmap.cc中:

cpp 复制代码
void* LargeObjectSpaceMmap::Allocate(size_t size, size_t* bytes_allocated) {
    // 使用mmap分配内存
    void* memory = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    if (memory == MAP_FAILED) {
        return nullptr;
    }
    
    // 设置内存保护
    if (!ProtectMemory(memory, size)) {
        // 保护失败,释放内存
        munmap(memory, size);
        return nullptr;
    }
    
    if (bytes_allocated != nullptr) {
        *bytes_allocated = size;
    }
    
    return memory;
}

bool LargeObjectSpaceMmap::ProtectMemory(void* memory, size_t size) {
    // 在内存块前后添加保护页,防止越界访问
    size_t page_size = getpagesize();
    
    // 计算保护页地址和大小
    void* guard_page_before = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(memory) - page_size);
    void* guard_page_after = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(memory) + size);
    
    // 设置保护页为不可访问
    if (mprotect(guard_page_before, page_size, PROT_NONE) != 0) {
        return false;
    }
    
    if (mprotect(guard_page_after, page_size, PROT_NONE) != 0) {
        // 恢复之前的保护设置
        mprotect(guard_page_before, page_size, PROT_READ | PROT_WRITE);
        return false;
    }
    
    return true;
}

9.2 内存泄漏检测

ART提供了内存泄漏检测机制,特别关注大对象的泄漏情况。在art/runtime/gc/heap.cc中:

cpp 复制代码
void Heap::CheckForMemoryLeaks() {
    // 记录当前所有大对象
    std::vector<mirror::Object*> large_objects = CollectLargeObjects();
    
    // 等待一段时间(如10秒)
    std::this_thread::sleep_for(std::chrono::seconds(10));
    
    // 再次记录大对象
    std::vector<mirror::Object*> new_large_objects = CollectLargeObjects();
    
    // 找出未被回收的大对象
    std::vector<mirror::Object*> leaked_objects = FindLeakedObjects(large_objects, new_large_objects);
    
    if (!leaked_objects.empty()) {
        LOG(WARNING) << "Detected " << leaked_objects.size() << " potential memory leaks:";
        for (mirror::Object* obj : leaked_objects) {
            LOG(WARNING) << "  Leaked object: " << obj->GetClass()->PrettyName()
                         << " size=" << obj->SizeOf();
        }
    }
}

这些安全机制确保了大对象分配和使用的可靠性,减少了因内存问题导致的应用崩溃和安全漏洞。

相关推荐
前端小巷子1 小时前
跨域问题解决方案:开发代理
前端·javascript·面试
天涯学馆1 小时前
JavaScript 跨域、事件循环、性能优化面试题解析教程
前端·javascript·面试
移动开发者1号1 小时前
ReLinker优化So库加载指南
android·kotlin
刘龙超1 小时前
如何应对 Android 面试官 -> 玩转 JetPack LiveData
android jetpack
山野万里__1 小时前
C++与Java内存共享技术:跨平台与跨语言实现指南
android·java·c++·笔记
Huckings1 小时前
Android 性能问题
android
晴殇i1 小时前
CSS 迎来重大升级:Chrome 137 支持 if () 条件函数,样式逻辑从此更灵活
前端·css·面试
Java技术小馆1 小时前
POST为什么发送两次请求
java·面试·架构
天天摸鱼的java工程师1 小时前
Java与AI:从业务场景到代码实现,构建人工客服系统实战
java·后端·面试
天涯学馆2 小时前
JS 组合模式在组件化开发中的应用:从原理到实战
前端·javascript·面试