码字不易,请大佬们点点关注,谢谢~
一、堆内存动态扩展概述
在Android系统中,应用程序的内存使用情况复杂多变,Android Runtime(ART)的堆内存动态扩展策略是保障应用稳定运行的关键机制。该策略允许堆内存根据应用实际需求动态调整大小,避免因内存不足导致应用崩溃,同时也能在内存使用低谷时释放资源,提高系统整体资源利用率。
从源码角度来看,堆内存动态扩展涉及art/runtime
目录下多个源文件协同工作。核心逻辑集中在heap.cc
和heap_tuning.cc
等文件中,通过一系列判断条件、内存分配与回收操作,实现堆内存的动态调整。接下来,我们将深入分析其原理与实现细节。
二、动态扩展的触发条件
2.1 内存分配失败触发
当应用尝试分配新对象时,如果当前堆内存无法满足分配需求,就会触发堆内存动态扩展。在art/runtime/heap/internals.cc
的Heap::AllocObject
方法中,详细实现了对象分配逻辑:
cpp
mirror::Object* Heap::AllocObject(Class* clazz, size_t extra_bytes,
PretenureFlag pretenure_flag,
bool from_quick_alloc) {
// 检查快速分配路径是否可用
if (from_quick_alloc && ShouldUseQuickAlloc(clazz)) {
// 尝试快速分配对象
mirror::Object* result = QuickAllocObject(clazz, extra_bytes, pretenure_flag);
if (result != nullptr) {
return result;
}
}
// 进入慢速分配路径,检查堆内存是否充足
if (AvailableMemory() < CalculateObjectSize(clazz, extra_bytes)) {
// 内存不足,触发动态扩展相关处理
HandleAllocationFailure(clazz, extra_bytes, pretenure_flag);
// 再次检查内存,尝试分配
if (AvailableMemory() >= CalculateObjectSize(clazz, extra_bytes)) {
return AllocateObjectFromHeap(clazz, extra_bytes, pretenure_flag);
}
}
// 内存仍然不足,返回空指针表示分配失败
return nullptr;
}
当AvailableMemory()
小于对象所需大小时,会调用HandleAllocationFailure
方法,该方法可能会触发垃圾回收或尝试扩展堆内存。如果垃圾回收后内存仍不足,就会启动堆内存动态扩展流程。
2.2 系统内存状态触发
除了内存分配失败,系统内存状态也会影响堆内存扩展。在art/runtime/gc/heap_tuning.cc
中,系统会定期监测整体内存使用情况:
cpp
void HeapTuner::MonitorSystemMemory() {
// 获取系统可用内存
size_t system_free_memory = GetSystemFreeMemory();
// 获取当前堆内存使用比例
float heap_usage_ratio = CalculateHeapUsageRatio();
// 判断系统内存是否紧张且堆内存使用接近阈值
if (system_free_memory < kSystemMemoryThreshold && heap_usage_ratio > kHeapUsageThreshold) {
// 触发堆内存扩展评估
EvaluateHeapExpansion();
}
}
当系统可用内存低于kSystemMemoryThreshold
,且堆内存使用比例超过kHeapUsageThreshold
时,会进行堆内存扩展评估,决定是否需要扩展堆内存以应对潜在的内存压力。
三、内存需求评估与扩展大小计算
3.1 内存需求评估
在触发堆内存扩展后,首先要评估当前实际内存需求。art/runtime/gc/heap_tuning.cc
中的EvaluateMemoryRequirement
方法负责此任务:
cpp
size_t HeapTuner::EvaluateMemoryRequirement() {
// 统计近期内存分配失败的对象大小总和
size_t failed_allocation_sum = CalculateFailedAllocationSum();
// 考虑未来一段时间内可能的内存增长趋势
size_t predicted_growth = PredictMemoryGrowth();
// 计算总内存需求
return failed_allocation_sum + predicted_growth;
}
该方法通过计算近期内存分配失败的对象大小总和,并结合对未来内存增长的预测,得出较为准确的内存需求值。其中,CalculateFailedAllocationSum
会遍历记录的内存分配失败信息,累加失败对象的大小;PredictMemoryGrowth
则基于历史内存使用数据和应用当前运行状态,预估未来内存增长情况。
3.2 扩展大小计算
确定内存需求后,需要计算合适的堆内存扩展大小。在art/runtime/gc/heap_tuning.cc
的CalculateHeapExpansionSize
方法中实现了扩展大小的计算逻辑:
cpp
size_t HeapTuner::CalculateHeapExpansionSize(size_t required_memory) {
// 获取当前堆内存大小
size_t current_heap_size = GetCurrentHeapSize();
// 计算扩展比例,这里假设一种简单的比例计算方式
float expansion_ratio = CalculateExpansionRatio(required_memory, current_heap_size);
// 计算扩展大小
return static_cast<size_t>(current_heap_size * expansion_ratio);
}
CalculateExpansionRatio
方法会根据内存需求和当前堆内存大小,综合考虑系统资源状况、应用优先级等因素,确定合适的扩展比例。例如,如果系统内存充足且应用对性能要求较高,可能会采用较大的扩展比例;反之,如果系统内存紧张,扩展比例会相对较小,以避免过度占用系统资源。
四、堆内存扩展的实现流程
4.1 内存申请
在确定扩展大小后,ART会向系统申请额外的内存。在art/runtime/heap.cc
的Heap::ExpandHeap
方法中实现了内存申请操作:
cpp
bool Heap::ExpandHeap(size_t size) {
// 使用mmap系统调用申请内存
void* new_memory = mmap(nullptr, size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (new_memory == MAP_FAILED) {
// 内存申请失败,处理错误情况
HandleMemoryAllocationFailure();
return false;
}
// 记录新申请的内存区域
AddMemoryRegion(new_memory, size);
return true;
}
通过mmap
系统调用,ART在虚拟内存空间中映射一段新的内存区域。如果申请失败,会调用HandleMemoryAllocationFailure
方法进行错误处理,可能包括释放已分配的部分内存、触发更激进的垃圾回收等操作。
4.2 内存整合与布局调整
申请到新内存后,需要将其整合到现有的堆内存中,并调整内存布局。在art/runtime/heap.cc
的Heap::IntegrateNewMemory
方法中实现了相关逻辑:
cpp
void Heap::IntegrateNewMemory(void* new_memory, size_t size) {
// 根据堆内存的分代布局,确定新内存的分配位置
if (ShouldAllocateToYoungGeneration(size)) {
// 分配到年轻代
young_space_->Expand(new_memory, size);
} else if (ShouldAllocateToOldGeneration(size)) {
// 分配到老年代
old_space_->Expand(new_memory, size);
} else {
// 分配到大对象空间
large_object_space_->Expand(new_memory, size);
}
// 更新堆内存的相关元数据,如总大小、各代空间大小等
UpdateHeapMetadata();
}
该方法会根据对象大小和分代策略,将新内存分配到合适的区域(年轻代、老年代或大对象空间)。分配完成后,更新堆内存的元数据,确保后续内存管理操作的准确性。
五、对象数据迁移与引用更新
5.1 对象数据迁移
当堆内存扩展后,部分对象可能需要迁移到新的内存区域,以充分利用新分配的内存空间。在年轻代或老年代扩展时,涉及对象数据迁移操作。以年轻代扩展为例,在art/runtime/gc/space/eden_space.cc
的EdenSpace::Expand
方法中实现了对象迁移逻辑:
cpp
void EdenSpace::Expand(void* new_memory, size_t size) {
// 保存当前Eden空间中的存活对象
std::vector<mirror::Object*> live_objects;
IterateObjects([&live_objects](mirror::Object* obj) {
if (obj->IsAlive()) {
live_objects.push_back(obj);
}
});
// 调整Eden空间的边界,纳入新内存
SetEnd(reinterpret_cast<uint8_t*>(GetEnd()) + size);
// 将存活对象迁移到新的内存位置
for (mirror::Object* obj : live_objects) {
void* new_location = AllocateMemoryForObject(obj);
memcpy(new_location, obj, obj->GetSize());
// 更新对象的内存地址
UpdateObjectAddress(obj, new_location);
}
}
该方法先遍历Eden空间,保存存活对象。然后调整Eden空间边界,将新内存纳入。最后将存活对象迁移到新内存位置,并更新对象的内存地址,确保对象的引用关系正确。
5.2 引用更新
对象数据迁移后,需要更新所有指向这些对象的引用,以保证应用程序能够正确访问对象。在art/runtime/gc/reference_table.cc
的ReferenceTable::UpdateReferences
方法中实现了引用更新逻辑:
cpp
void ReferenceTable::UpdateReferences() {
// 遍历引用表中的所有引用
for (auto& entry : references_) {
mirror::Object* obj = entry.second;
// 检查对象是否已迁移
if (obj->HasBeenMoved()) {
// 获取对象的新地址
void* new_address = obj->GetNewAddress();
// 更新引用指向的地址
entry.second = reinterpret_cast<mirror::Object*>(new_address);
}
}
}
该方法遍历引用表,检查每个引用指向的对象是否已迁移。如果对象已迁移,获取其新地址并更新引用,确保应用程序中的所有引用都指向正确的对象位置。
六、动态扩展与垃圾回收的协同工作
6.1 扩展前的垃圾回收
在触发堆内存扩展前,ART通常会先尝试执行垃圾回收,以释放部分内存,减少扩展需求。在art/runtime/gc/heap.cc
的HandleAllocationFailure
方法中,当内存分配失败时,会优先触发垃圾回收:
cpp
void Heap::HandleAllocationFailure(Class* clazz, size_t extra_bytes, PretenureFlag pretenure_flag) {
// 尝试执行年轻代垃圾回收
if (ShouldTriggerMinorGc()) {
Scavenger scavenger(this);
scavenger.Collect(kGcTypeYoung, kGcCauseAllocationFailure);
}
// 如果年轻代垃圾回收后内存仍不足,尝试执行老年代垃圾回收
if (AvailableMemory() < CalculateObjectSize(clazz, extra_bytes) && ShouldTriggerMajorGc()) {
MarkSweepCompact marksweep_compact(this);
marksweep_compact.Collect(kGcTypeOld, kGcCauseAllocationFailure);
}
// 如果垃圾回收后内存仍不足,才进行堆内存扩展
if (AvailableMemory() < CalculateObjectSize(clazz, extra_bytes)) {
HeapTuner tuner(this);
size_t expansion_size = tuner.CalculateHeapExpansionSize(CalculateObjectSize(clazz, extra_bytes));
ExpandHeap(expansion_size);
}
}
通过先执行垃圾回收,尽可能释放已占用的内存,减少堆内存扩展的频率和大小,提高内存使用效率。
6.2 扩展后的垃圾回收优化
堆内存扩展后,内存布局发生变化,此时垃圾回收算法也会进行相应优化。例如,在对象数据迁移后,垃圾回收器会利用新的内存布局信息,更高效地标记和回收垃圾对象。在art/runtime/gc/collector/marksweep_compact.cc
的MarkSweepCompact::Collect
方法中,会根据扩展后的内存布局调整标记和整理策略:
cpp
void MarkSweepCompact::Collect(GcType gc_type, GcCause gc_cause) {
// 更新内存布局信息
UpdateMemoryLayoutInfo();
// 执行标记阶段
MarkObjects();
// 根据新的内存布局执行更优化的整理操作
CompactObjectsWithNewLayout();
// 更新对象引用关系
UpdateReferences();
}
UpdateMemoryLayoutInfo
方法会获取扩展后的内存布局信息,CompactObjectsWithNewLayout
方法则根据新布局进行更高效的对象整理,减少内存碎片,提高内存利用率。
七、多线程环境下的动态扩展处理
7.1 并发控制机制
在多线程环境下,多个线程可能同时触发堆内存扩展,为了保证扩展操作的正确性和一致性,ART采用了并发控制机制。在art/runtime/heap.cc
的Heap::ExpandHeap
方法中,使用互斥锁来保护堆内存扩展操作:
cpp
bool Heap::ExpandHeap(size_t size) {
std::lock_guard<std::mutex> lock(heap_mutex_);
// 使用mmap系统调用申请内存
void* new_memory = mmap(nullptr, size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (new_memory == MAP_FAILED) {
// 内存申请失败,处理错误情况
HandleMemoryAllocationFailure();
return false;
}
// 记录新申请的内存区域
AddMemoryRegion(new_memory, size);
return true;
}
通过std::lock_guard
在扩展操作期间锁定heap_mutex_
,确保同一时刻只有一个线程能够执行堆内存扩展,避免多个线程同时申请内存导致的冲突和错误。
7.2 线程安全的数据迁移与引用更新
在多线程环境下进行对象数据迁移和引用更新时,也需要保证线程安全。在art/runtime/gc/reference_table.cc
的ReferenceTable::UpdateReferences
方法中,使用读写锁来保护引用表的更新操作:
cpp
void ReferenceTable::UpdateReferences() {
std::unique_lock<std::shared_mutex> lock(reference_table_mutex_);
// 遍历引用表中的所有引用
for (auto& entry : references_) {
mirror::Object* obj = entry.second;
// 检查对象是否已迁移
if (obj->HasBeenMoved()) {
// 获取对象的新地址
void* new_address = obj->GetNewAddress();
// 更新引用指向的地址
entry.second = reinterpret_cast<mirror::Object*>(new_address);
}
}
}
std::unique_lock
在更新引用表时获取写锁,确保在更新过程中其他线程无法同时修改引用表,保证引用更新操作的线程安全性。
八、动态扩展策略的性能优化
8.1 减少扩展频率
频繁的堆内存扩展会带来较大的性能开销,ART通过多种方式减少扩展频率。在art/runtime/gc/heap_tuning.cc
中,通过优化内存需求评估算法,更准确地预测内存需求,避免不必要的扩展:
cpp
size_t HeapTuner::PredictMemoryGrowth() {
// 分析历史内存使用数据,采用更复杂的预测模型
std::vector<size_t> historical_usage = GetHistoricalMemoryUsage();
// 使用时间序列分析等算法预测未来内存增长
size_t predicted_growth = AnalyzeHistoricalData(historical_usage);
return predicted_growth;
}
通过更准确的内存增长预测,避免因预测不准确导致的频繁扩展。同时,在垃圾回收过程中,采用更高效的回收策略,尽量释放更多内存,减少扩展需求。
8.2 优化内存分配与迁移效率
在堆内存扩展过程中,优化内存分配和对象迁移效率可以显著提升性能。在内存分配方面,采用更高效的内存分配算法,如伙伴系统(Buddy System)或 slab 分配器,减少内存分配的时间开销。在对象迁移方面,采用并行迁移技术,利用多核处理器的优势,加快对象迁移速度。在art/runtime/gc/space/eden_space.cc
的EdenSpace::Expand
方法中,可以引入并行迁移逻辑:
cpp
void EdenSpace::Expand(void* new_memory, size_t size) {
// 保存当前Eden空间中的存活对象
std::vector<mirror::Object*> live_objects;
IterateObjects([&live_objects](mirror::Object* obj) {
if (obj->IsAlive()) {
live_objects.push_back(obj);
}
});
// 调整Eden空间的边界,纳入新内存
SetEnd(reinterpret_cast<uint8_t*>(GetEnd()) + size);
// 并行迁移存活对象
std::vector<std::thread> threads;
const size_t num_threads = std::thread::hardware_concurrency();
const size_t batch_size = live_objects.size() / num_threads;
for (size_t i = 0; i < num_threads; ++i) {
size_t start = i * batch_size;
size_t end = (i == num_threads - 1)? live_objects.size() : (i + 1) * batch_size;
threads.emplace_back([this, &live_objects, start, end]() {
for (size_t j = start; j < end; ++j) {
mirror::Object* obj = live_objects[j];
void* new_location = AllocateMemory
九、动态扩展与系统资源的交互协调
9.1 与Linux内核的内存交互
Android Runtime的堆内存动态扩展离不开与Linux内核的紧密交互。ART通过系统调用向Linux内核申请内存资源,最常用的是mmap
系统调用。在art/runtime/heap.cc
的Heap::ExpandHeap
方法中,通过mmap
实现内存申请:
cpp
bool Heap::ExpandHeap(size_t size) {
// 使用mmap系统调用申请匿名内存,PROT_READ | PROT_WRITE表示内存可读可写,
// MAP_PRIVATE | MAP_ANONYMOUS表示创建私有匿名映射
void* new_memory = mmap(nullptr, size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (new_memory == MAP_FAILED) {
// 内存申请失败,调用错误处理函数
HandleMemoryAllocationFailure();
return false;
}
// 将新申请的内存区域添加到堆内存管理范围
AddMemoryRegion(new_memory, size);
return true;
}
mmap
系统调用返回一个指向新分配内存区域的指针,如果返回MAP_FAILED
,则表示内存申请失败。此时,ART会调用HandleMemoryAllocationFailure
方法进行处理,可能包括触发更激进的垃圾回收、释放部分已分配内存,或者向应用抛出内存不足的异常。
当ART不再需要某些内存时,会通过munmap
系统调用将内存归还给Linux内核。这种交互方式使得ART能够在Linux内核提供的内存管理框架下,灵活地调整堆内存大小,同时也依赖于内核的内存分配策略和资源调度机制 。例如,当系统内存紧张时,Linux内核可能会对ART的内存申请进行限制,导致mmap
调用失败,此时ART需要采取相应的应对措施。
9.2 与其他进程的资源竞争处理
在Android系统中,多个应用进程同时运行,它们之间存在内存资源竞争。ART的堆内存动态扩展策略需要考虑与其他进程的资源协调,避免过度占用内存导致系统整体性能下降或其他进程崩溃。
系统会根据进程的优先级和资源使用情况进行调度。对于前台应用进程,系统会优先保障其内存需求;而对于后台进程,在内存紧张时可能会被系统杀死以释放内存。ART在进行堆内存扩展时,会参考系统的进程优先级信息。在art/runtime/gc/heap_tuning.cc
中,可能存在相关逻辑来判断当前进程的优先级:
cpp
void HeapTuner::EvaluateHeapExpansion() {
// 获取当前进程的优先级
int process_priority = GetProcessPriority();
// 如果是前台进程,且内存需求迫切,可以适当放宽扩展条件
if (process_priority == FOREGROUND_PRIORITY && IsMemoryDemandUrgent()) {
size_t expansion_size = CalculateHeapExpansionSize(EstimateMemoryRequirement());
Heap::Current()->ExpandHeap(expansion_size);
} else {
// 对于后台进程或内存需求不紧急的情况,谨慎进行扩展
// 可能采取更保守的扩展策略,或者等待系统资源更充裕时再扩展
}
}
此外,当系统内存不足时,Linux内核会通过OOM(Out Of Memory) killer机制杀死某些进程来释放内存。ART会尽量避免自身进程因内存使用不当而被OOM killer选中。通过合理的堆内存动态扩展策略,以及与系统内存管理机制的协同,ART可以在保证自身应用正常运行的同时,维护系统的整体稳定性 。
十、动态扩展策略在不同场景下的应用与挑战
10.1 高并发应用场景
在高并发应用场景下,如即时通讯应用、在线游戏等,多个线程会同时进行大量的内存分配和对象创建操作,这对堆内存动态扩展策略提出了更高的要求。
高并发场景下,线程之间对内存资源的竞争加剧,容易导致频繁的内存分配失败,从而触发堆内存扩展。为了应对这种情况,ART在多线程并发控制方面采用了更精细的锁机制和优化策略。除了前文提到的互斥锁和读写锁,还会使用无锁数据结构和CAS(Compare and Swap)操作来减少锁竞争带来的性能损耗。
在对象数据迁移和引用更新过程中,高并发也增加了复杂性。例如,当多个线程同时迁移对象时,需要确保数据的一致性和完整性。ART可能会采用版本号机制或事务性内存的思想,对对象迁移和引用更新操作进行管理。每个对象维护一个版本号,在迁移和更新引用时,只有版本号匹配的操作才能成功执行,避免因并发操作导致的数据错误 。
然而,高并发场景下的动态扩展也面临挑战。频繁的扩展操作会带来较大的性能开销,包括内存申请、数据迁移和引用更新等操作的时间成本。此外,锁竞争和同步机制也会影响应用的并发性能。因此,如何在保证内存充足的前提下,尽量减少扩展频率,优化并发控制策略,是高并发场景下堆内存动态扩展策略需要解决的关键问题 。
10.2 内存敏感型应用场景
对于内存敏感型应用,如嵌入式设备上的轻量级应用、运行在低配置设备上的应用等,内存资源非常有限,堆内存动态扩展策略需要更加谨慎。
在这类应用中,ART会采用更严格的内存需求评估算法,尽量准确地预测内存需求,避免过度扩展堆内存。可能会采用更保守的扩展比例,并且在扩展前会进行更充分的垃圾回收,尽可能释放已占用的内存。在art/runtime/gc/heap_tuning.cc
中,可能会有特殊的配置和算法来适应内存敏感型应用:
cpp
void HeapTuner::TuneForMemorySensitiveApp() {
// 降低扩展比例阈值
SetExpansionRatioThreshold(LOW_MEMORY_EXPANSION_RATIO);
// 增加垃圾回收的频率和强度
IncreaseGCRecoveryEfficiency();
// 优化内存需求预测模型,使其更精准
OptimizeMemoryRequirementPredictionModel();
}
内存敏感型应用场景下的挑战在于,既要保证应用的正常运行,满足其内存需求,又不能过度占用内存导致系统资源耗尽。此外,由于设备性能有限,垃圾回收和内存扩展等操作对应用性能的影响更为显著,需要在内存管理和性能之间找到更好的平衡点 。同时,还需要考虑与其他系统组件的资源共享和协调,确保整个系统的稳定运行 。
十一、动态扩展策略的发展与演进
11.1 从Dalvik到ART的策略变化
在Android系统从Dalvik虚拟机过渡到ART运行时的过程中,堆内存动态扩展策略发生了显著的变化。
Dalvik采用基于JIT(Just - In - Time)编译的方式,其内存管理机制相对简单。在堆内存扩展方面,Dalvik的策略较为被动,通常在内存分配失败时才会进行扩展,并且扩展过程中的优化措施较少。垃圾回收算法也相对低效,导致在内存紧张时,堆内存扩展频繁,应用性能下降明显。
而ART采用AOT(Ahead - Of - Time)编译技术,对内存管理进行了全面优化。ART的堆内存动态扩展策略更加主动和智能。在内存需求评估方面,引入了更复杂的预测模型,能够更准确地预估内存增长趋势。在扩展过程中,采用了更高效的内存分配算法和对象迁移策略,减少了扩展操作带来的性能开销。例如,ART的分代式垃圾回收机制与动态扩展策略紧密结合,年轻代、老年代和大对象空间的扩展方式和时机都经过精心设计,提高了内存使用效率 。
此外,ART在多线程并发控制和与系统资源交互方面也进行了大量改进,使得堆内存动态扩展策略在多进程环境下更加稳定和高效,这是Dalvik所无法比拟的。