码字不易,请大佬们点点关注,谢谢~
一、大对象定义与分类标准
在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应用中主要包括以下几类:
- 大型数组:如图片解码后的Bitmap数据、音频/视频缓冲区等
- 长字符串:如大型JSON数据、HTML文档等
- 大型集合对象:如包含大量元素的ArrayList、HashMap等
- 自定义大型对象:某些应用特有的大型数据结构
这些大对象在内存中占用连续空间,对内存分配和垃圾回收机制提出了特殊挑战。
1.2 大对象对内存管理的挑战
大对象的存在对内存管理系统带来了多方面的挑战:
- 外部碎片化:大对象的分配和释放容易导致内存空间碎片化,使得后续即使有足够的总内存,也无法分配连续的大块内存
- GC压力:大对象的复制和移动操作成本高昂,会显著增加垃圾回收的停顿时间
- 分配失败风险:由于需要连续内存空间,大对象分配失败的概率远高于小对象
为应对这些挑战,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采用了多种策略:
- 大对象合并 :在释放大对象时,尝试合并相邻的空闲块。在
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);
}
-
内存整理:定期执行内存整理,将存活的大对象移动到一起,形成更大的连续空闲空间。
-
大对象预分配:通过预分配大对象空间,减少运行时的碎片化风险。
六、大对象分配失败处理机制
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会执行以下处理流程:
-
触发垃圾回收:尝试回收未使用的内存。
-
内存压缩:执行内存压缩,减少碎片化。
-
杀死后台进程:在极端情况下,杀死后台进程以释放更多内存。
在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提供了多种工具用于监控大对象的分配和使用情况:
-
内存分析工具:如Android Profiler,可以可视化大对象的分配和回收情况。
-
GC日志:记录大对象相关的GC事件,帮助分析性能问题。
-
堆转储(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的实现原理,以下是一些性能调优建议:
-
避免创建过大对象:尽量将大对象拆分为多个小对象,减少内存压力。
-
重用大对象:对于需要频繁使用的大对象(如缓冲区),考虑实现对象池进行重用。
-
优化大对象生命周期:及时释放不再使用的大对象,避免长时间占用内存。
-
监控大对象分配模式:通过分析大对象分配模式,提前预分配或调整分配策略。
-
调整大对象阈值:根据应用特性,适当调整大对象阈值,平衡分配效率和碎片化。
八、大对象与内存压缩
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 大对象压缩的性能考量
大对象的压缩操作成本较高,主要体现在以下几个方面:
-
内存带宽消耗:移动大对象需要大量内存带宽,可能影响应用性能。
-
STW时间增加:大对象压缩通常需要在STW(Stop The World)阶段进行,延长了应用暂停时间。
-
缓存失效:大对象移动后,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();
}
}
}
这些安全机制确保了大对象分配和使用的可靠性,减少了因内存问题导致的应用崩溃和安全漏洞。