码字不易,请大佬们点点关注,谢谢~
一、垃圾回收基础概念与标记-清除算法定位
垃圾回收(Garbage Collection,GC)是现代编程语言运行时环境的核心功能之一,其主要目标是自动管理内存资源,回收不再使用的对象所占用的内存空间。在Android Runtime(ART)中,垃圾回收机制对于系统性能和稳定性至关重要,特别是在移动设备内存资源有限的情况下。
标记-清除(Mark-Sweep)算法是垃圾回收算法中的基础算法,也是许多现代垃圾回收器的核心组成部分。该算法主要分为两个阶段:标记阶段和清除阶段。在标记阶段,垃圾回收器会从根对象(如静态变量、栈变量等)开始,遍历所有可达对象,并标记这些对象为存活状态;在清除阶段,垃圾回收器会扫描整个堆内存,回收所有未被标记的对象所占用的内存空间。
标记-清除算法的优点是实现简单,不需要移动对象,因此不会产生对象移动带来的开销。然而,该算法也存在明显的缺点,即会产生内存碎片,导致后续内存分配效率降低。在ART中,标记-清除算法通常作为基础算法被其他更复杂的垃圾回收算法所继承和优化,例如标记-整理(Mark-Compact)算法和分代垃圾回收算法等。
二、Android Runtime垃圾回收架构概述
Android Runtime(ART)的垃圾回收架构是一个复杂的系统,旨在高效地管理应用程序的内存使用。ART的垃圾回收器采用了多种技术和策略,以适应不同的应用场景和性能需求。
ART的垃圾回收器主要由以下几个部分组成:
-
垃圾回收器核心引擎:负责协调和控制整个垃圾回收过程,包括标记阶段、清除阶段等核心操作。
-
内存管理模块:负责堆内存的分配和管理,包括对象的分配、内存区域的划分等。
-
对象引用管理:负责处理对象之间的引用关系,包括强引用、弱引用、软引用等不同类型的引用。
-
并发控制模块:负责处理多线程环境下的垃圾回收操作,确保垃圾回收过程与应用程序的正常运行互不干扰。
-
性能监控和调优模块:负责监控垃圾回收的性能指标,并根据实际情况进行调优,以提高垃圾回收的效率和应用程序的性能。
ART的垃圾回收器支持多种垃圾回收模式,包括并发垃圾回收(Concurrent GC)、部分并发垃圾回收(Partial Concurrent GC)和停顿式垃圾回收(Stop-the-World GC)等。不同的垃圾回收模式适用于不同的场景,可以根据应用程序的性能需求和内存使用情况进行选择。
三、标记-清除算法核心流程解析
标记-清除算法的核心流程主要包括标记阶段和清除阶段。下面将详细解析这两个阶段的工作原理。
3.1 标记阶段
标记阶段是标记-清除算法的第一个阶段,其主要目标是从根对象开始,遍历所有可达对象,并标记这些对象为存活状态。
标记阶段的工作流程如下:
-
初始化标记上下文:垃圾回收器首先会初始化标记上下文,包括创建标记栈、标记位图等数据结构,用于记录标记过程中的中间状态。
-
根对象枚举:垃圾回收器会枚举所有根对象,包括静态变量、栈变量、JNI引用等。这些根对象是垃圾回收的起点。
-
根对象标记:垃圾回收器会从根对象开始,递归地标记所有可达对象。具体来说,垃圾回收器会将根对象压入标记栈,并标记这些根对象为存活状态。
-
对象遍历与标记:垃圾回收器会从标记栈中弹出对象,并遍历该对象的所有引用字段。对于每个引用字段,如果其指向的对象尚未被标记,则将该对象标记为存活状态,并将其压入标记栈。
-
标记栈处理:重复步骤4,直到标记栈为空。此时,所有可达对象都已经被标记为存活状态。
标记阶段的核心代码如下:
c
// 标记阶段的核心函数
void MarkSweepCollector::MarkPhase() {
// 初始化标记上下文
InitializeMarkContext();
// 枚举并标记根对象
EnumerateRoots();
// 处理标记栈,遍历所有可达对象
ProcessMarkStack();
// 完成标记阶段
FinalizeMarkPhase();
}
// 初始化标记上下文
void MarkSweepCollector::InitializeMarkContext() {
// 创建标记栈
mark_stack_ = new MarkStack();
// 创建标记位图,用于记录对象的标记状态
mark_bitmap_ = heap_->CreateMarkBitmap();
// 初始化其他标记上下文相关的数据结构
// ...
}
// 枚举并标记根对象
void MarkSweepCollector::EnumerateRoots() {
// 枚举静态变量
EnumerateStaticRoots();
// 枚举线程栈变量
EnumerateThreadRoots();
// 枚举JNI引用
EnumerateJNIRoots();
// 枚举其他根对象
// ...
}
// 处理标记栈,遍历所有可达对象
void MarkSweepCollector::ProcessMarkStack() {
while (!mark_stack_->IsEmpty()) {
Object* obj = mark_stack_->Pop();
// 遍历对象的所有引用字段
ObjectReferenceVisitor visitor(this);
obj->VisitReferences(visitor);
}
}
// 对象引用访问器,用于遍历对象的引用字段
class ObjectReferenceVisitor {
public:
ObjectReferenceVisitor(MarkSweepCollector* collector) : collector_(collector) {}
// 访问对象的引用字段
void VisitReference(Object** ref) {
Object* obj = *ref;
if (obj != nullptr && !collector_->IsMarked(obj)) {
// 标记对象为存活状态
collector_->MarkObject(obj);
// 将对象压入标记栈
collector_->PushToMarkStack(obj);
}
}
private:
MarkSweepCollector* collector_;
};
3.2 清除阶段
清除阶段是标记-清除算法的第二个阶段,其主要目标是扫描整个堆内存,回收所有未被标记的对象所占用的内存空间。
清除阶段的工作流程如下:
-
初始化清除上下文:垃圾回收器首先会初始化清除上下文,包括设置堆内存的扫描范围等。
-
堆内存扫描:垃圾回收器会扫描整个堆内存,检查每个对象的标记状态。
-
对象回收:对于未被标记的对象,垃圾回收器会回收其占用的内存空间,并将其添加到空闲内存列表中,以便后续内存分配使用。
-
标记状态重置:在回收完所有未被标记的对象后,垃圾回收器会重置所有对象的标记状态,为下一次垃圾回收做准备。
清除阶段的核心代码如下:
c
// 清除阶段的核心函数
void MarkSweepCollector::SweepPhase() {
// 初始化清除上下文
InitializeSweepContext();
// 扫描并回收未被标记的对象
SweepHeap();
// 重置标记状态
ResetMarkBitmap();
// 完成清除阶段
FinalizeSweepPhase();
}
// 初始化清除上下文
void MarkSweepCollector::InitializeSweepContext() {
// 设置堆内存的扫描范围
heap_start_ = heap_->GetStart();
heap_end_ = heap_->GetEnd();
// 初始化空闲内存列表
free_list_ = new FreeList();
// 初始化其他清除上下文相关的数据结构
// ...
}
// 扫描并回收未被标记的对象
void MarkSweepCollector::SweepHeap() {
Object* obj = reinterpret_cast<Object*>(heap_start_);
while (obj < reinterpret_cast<Object*>(heap_end_)) {
// 获取对象的大小
size_t obj_size = obj->GetSize();
if (!IsMarked(obj)) {
// 对象未被标记,回收其占用的内存空间
FreeObject(obj, obj_size);
} else {
// 对象已被标记,重置其标记状态
UnmarkObject(obj);
}
// 移动到下一个对象
obj = reinterpret_cast<Object*>(reinterpret_cast<uint8_t*>(obj) + obj_size);
}
}
// 回收对象占用的内存空间
void MarkSweepCollector::FreeObject(Object* obj, size_t size) {
// 将对象添加到空闲内存列表
free_list_->Add(obj, size);
// 执行其他回收相关的操作
// ...
}
// 重置标记位图
void MarkSweepCollector::ResetMarkBitmap() {
// 清空标记位图,为下一次垃圾回收做准备
mark_bitmap_->Clear();
}
四、标记-清除算法的内存管理策略
标记-清除算法的内存管理策略主要涉及内存分配和内存回收两个方面。下面将详细解析这两个方面的工作原理。
4.1 内存分配策略
在标记-清除算法中,内存分配主要采用空闲列表(Free List)策略。空闲列表是一种用于管理空闲内存块的数据结构,它将所有空闲内存块组织成一个链表,以便快速查找和分配合适的内存块。
当应用程序请求分配内存时,垃圾回收器会从空闲列表中查找合适的空闲内存块。如果找到合适的内存块,则将其分配给应用程序,并从空闲列表中移除该内存块;如果没有找到合适的内存块,则需要进行垃圾回收操作,以释放更多的内存空间。
空闲列表策略的核心代码如下:
c
// 空闲列表类
class FreeList {
public:
FreeList() : head_(nullptr) {}
// 添加空闲内存块到列表
void Add(void* block, size_t size) {
FreeBlock* free_block = new (block) FreeBlock(size);
free_block->next = head_;
head_ = free_block;
}
// 从列表中分配内存块
void* Allocate(size_t size) {
FreeBlock* prev = nullptr;
FreeBlock* current = head_;
while (current != nullptr) {
if (current->size >= size) {
// 找到合适的内存块
if (prev == nullptr) {
head_ = current->next;
} else {
prev->next = current->next;
}
// 如果内存块比需要的大,分割内存块
if (current->size > size + sizeof(FreeBlock)) {
void* allocated = current;
size_t remaining_size = current->size - size;
void* remaining_block = reinterpret_cast<uint8_t*>(allocated) + size;
// 将剩余的内存块添加回空闲列表
Add(remaining_block, remaining_size);
return allocated;
} else {
return current;
}
}
prev = current;
current = current->next;
}
// 没有找到合适的内存块
return nullptr;
}
private:
// 空闲内存块结构
struct FreeBlock {
size_t size;
FreeBlock* next;
explicit FreeBlock(size_t s) : size(s), next(nullptr) {}
};
FreeBlock* head_; // 空闲列表头指针
};
4.2 内存回收策略
在标记-清除算法中,内存回收主要在清除阶段进行。当垃圾回收器扫描堆内存时,会将未被标记的对象所占用的内存空间回收,并添加到空闲列表中。
为了提高内存回收的效率,标记-清除算法通常会采用一些优化策略,例如:
-
合并相邻的空闲内存块:当回收一个内存块时,如果该内存块与相邻的空闲内存块相邻,则将它们合并成一个更大的空闲内存块,以减少内存碎片。
-
分代回收:将对象按生命周期分为不同的代,对不同代的对象采用不同的垃圾回收策略。例如,新生代对象生命周期较短,垃圾回收频率较高;老年代对象生命周期较长,垃圾回收频率较低。
-
内存整理:在某些情况下,标记-清除算法会结合内存整理技术,将存活的对象移动到一起,以消除内存碎片。
内存回收策略的核心代码如下:
c
// 扫描并回收未被标记的对象,同时合并相邻的空闲内存块
void MarkSweepCollector::SweepHeap() {
Object* obj = reinterpret_cast<Object*>(heap_start_);
FreeBlock* previous_free_block = nullptr;
while (obj < reinterpret_cast<Object*>(heap_end_)) {
// 获取对象的大小
size_t obj_size = obj->GetSize();
if (!IsMarked(obj)) {
// 对象未被标记,回收其占用的内存空间
FreeBlock* current_free_block = reinterpret_cast<FreeBlock*>(obj);
current_free_block->size = obj_size;
current_free_block->next = nullptr;
// 尝试合并相邻的空闲内存块
if (previous_free_block != nullptr &&
reinterpret_cast<uint8_t*>(previous_free_block) + previous_free_block->size ==
reinterpret_cast<uint8_t*>(current_free_block)) {
// 合并当前空闲块到前一个空闲块
previous_free_block->size += current_free_block->size;
} else {
// 添加新的空闲块
free_list_->Add(current_free_block);
previous_free_block = current_free_block;
}
} else {
// 对象已被标记,重置其标记状态
UnmarkObject(obj);
previous_free_block = nullptr;
}
// 移动到下一个对象
obj = reinterpret_cast<Object*>(reinterpret_cast<uint8_t*>(obj) + obj_size);
}
}
五、并发标记-清除技术实现
为了减少垃圾回收对应用程序的影响,Android Runtime(ART)实现了并发标记-清除(Concurrent Mark-Sweep)技术。并发标记-清除技术允许垃圾回收过程与应用程序的正常运行并发进行,从而减少应用程序的停顿时间。
并发标记-清除技术的核心思想是将标记阶段和清除阶段分为多个子阶段,并在应用程序运行的间隙执行这些子阶段。具体来说,并发标记-清除技术主要包括以下几个关键步骤:
-
初始标记阶段:暂停应用程序的执行,标记所有根对象。这个阶段的停顿时间很短。
-
并发标记阶段:允许应用程序继续运行,同时垃圾回收器并发地标记所有可达对象。在这个阶段,应用程序可能会修改对象的引用关系,因此需要使用写屏障(Write Barrier)来跟踪这些修改。
-
重新标记阶段:暂停应用程序的执行,处理并发标记阶段中由写屏障记录的引用修改,确保所有可达对象都被标记。这个阶段的停顿时间也比较短。
-
并发清除阶段:允许应用程序继续运行,同时垃圾回收器并发地清除未被标记的对象。
并发标记-清除技术的核心代码如下:
c
// 并发标记-清除收集器类
class ConcurrentMarkSweepCollector : public MarkSweepCollector {
public:
// 执行垃圾回收
void CollectGarbage() override {
// 初始标记阶段
InitialMark();
// 并发标记阶段
ConcurrentMark();
// 重新标记阶段
Remark();
// 并发清除阶段
ConcurrentSweep();
}
// 初始标记阶段
void InitialMark() {
// 暂停应用程序
SuspendAllThreads();
// 初始化标记上下文
InitializeMarkContext();
// 枚举并标记根对象
EnumerateRoots();
// 恢复应用程序
ResumeAllThreads();
}
// 并发标记阶段
void ConcurrentMark() {
// 创建并启动并发标记线程
mark_thread_ = new Thread(&ConcurrentMarkThread, this);
mark_thread_->Start();
// 主线程继续执行其他任务
// ...
// 等待并发标记线程完成
mark_thread_->Join();
}
// 并发标记线程函数
static void* ConcurrentMarkThread(void* arg) {
ConcurrentMarkSweepCollector* collector =
reinterpret_cast<ConcurrentMarkSweepCollector*>(arg);
collector->RunConcurrentMark();
return nullptr;
}
// 执行并发标记
void RunConcurrentMark() {
// 处理标记栈,遍历所有可达对象
ProcessMarkStack();
// 完成并发标记
FinalizeConcurrentMark();
}
// 重新标记阶段
void Remark() {
// 暂停应用程序
SuspendAllThreads();
// 处理写屏障记录的引用修改
ProcessWriteBarrierLog();
// 完成重新标记
FinalizeRemark();
// 恢复应用程序
ResumeAllThreads();
}
// 并发清除阶段
void ConcurrentSweep() {
// 创建并启动并发清除线程
sweep_thread_ = new Thread(&ConcurrentSweepThread, this);
sweep_thread_->Start();
// 主线程继续执行其他任务
// ...
// 等待并发清除线程完成
sweep_thread_->Join();
}
// 并发清除线程函数
static void* ConcurrentSweepThread(void* arg) {
ConcurrentMarkSweepCollector* collector =
reinterpret_cast<ConcurrentMarkSweepCollector*>(arg);
collector->RunConcurrentSweep();
return nullptr;
}
// 执行并发清除
void RunConcurrentSweep() {
// 初始化清除上下文
InitializeSweepContext();
// 扫描并回收未被标记的对象
SweepHeap();
// 重置标记状态
ResetMarkBitmap();
// 完成并发清除
FinalizeConcurrentSweep();
}
private:
Thread* mark_thread_; // 并发标记线程
Thread* sweep_thread_; // 并发清除线程
};
六、写屏障技术在标记-清除中的应用
写屏障(Write Barrier)是并发垃圾回收中的一项关键技术,用于在应用程序修改对象引用关系时跟踪这些修改,确保垃圾回收器能够正确标记所有可达对象。
在并发标记-清除算法中,写屏障主要用于解决以下两个问题:
-
并发标记过程中的对象引用修改:在并发标记阶段,应用程序可能会修改对象的引用关系,导致某些可达对象未被标记。写屏障通过记录这些引用修改,确保在重新标记阶段能够正确处理这些修改。
-
新生代对象对老年代对象的引用:在分代垃圾回收中,新生代对象可能会引用老年代对象。为了避免在每次新生代垃圾回收时扫描整个老年代,写屏障会记录新生代对象对老年代对象的引用,从而在新生代垃圾回收时只需扫描这些被引用的老年代对象。
Android Runtime(ART)实现了多种写屏障技术,其中最常用的是增量更新(Incremental Update)写屏障和原始快照(Snapshot-at-the-Beginning,SATB)写屏障。
增量更新写屏障的核心思想是:当应用程序将一个引用字段从指向一个对象A改为指向另一个对象B时,写屏障会记录这个修改,并确保对象B在重新标记阶段被标记。
原始快照写屏障的核心思想是:在并发标记开始时,记录所有对象的初始状态(即快照)。当应用程序修改对象的引用关系时,写屏障会确保被修改的旧引用对象在重新标记阶段被标记。
写屏障的核心代码如下:
c
// 写屏障基类
class WriteBarrier {
public:
virtual ~WriteBarrier() {}
// 处理对象引用的写操作
virtual void WriteObject(Object** ref, Object* new_value) = 0;
// 处理数组元素的写操作
virtual void WriteArrayElement(Object* array, size_t index, Object* new_value) = 0;
};
// 增量更新写屏障
class IncrementalUpdateWriteBarrier : public WriteBarrier {
public:
IncrementalUpdateWriteBarrier(MarkSweepCollector* collector) : collector_(collector) {}
// 处理对象引用的写操作
void WriteObject(Object** ref, Object* new_value) override {
Object* old_value = *ref;
// 如果新值不为空且未被标记,则记录这个引用
if (new_value != nullptr && !collector_->IsMarked(new_value)) {
collector_->RecordReference(ref, new_value);
}
// 更新引用
*ref = new_value;
}
// 处理数组元素的写操作
void WriteArrayElement(Object* array, size_t index, Object* new_value) override {
// 获取数组元素的引用
Object** element_ref = GetArrayElementRef(array, index);
Object* old_value = *element_ref;
// 如果新值不为空且未被标记,则记录这个引用
if (new_value != nullptr && !collector_->IsMarked(new_value)) {
collector_->RecordReference(element_ref, new_value);
}
// 更新数组元素
*element_ref = new_value;
}
private:
MarkSweepCollector* collector_;
};
// 原始快照写屏障
class SATBWriteBarrier : public WriteBarrier {
public:
SATBWriteBarrier(MarkSweepCollector* collector) : collector_(collector) {}
// 处理对象引用的写操作
void WriteObject(Object** ref, Object* new_value) override {
Object* old_value = *ref;
// 如果旧值不为空且未被标记,则记录这个旧值
if (old_value != nullptr && !collector_->IsMarked(old_value)) {
collector_->RecordPreWriteValue(old_value);
}
// 更新引用
*ref = new_value;
}
// 处理数组元素的写操作
void WriteArrayElement(Object* array, size_t index, Object* new_value) override {
// 获取数组元素的引用
Object** element_ref = GetArrayElementRef(array, index);
Object* old_value = *element_ref;
// 如果旧值不为空且未被标记,则记录这个旧值
if (old_value != nullptr && !collector_->IsMarked(old_value)) {
collector_->RecordPreWriteValue(old_value);
}
// 更新数组元素
*element_ref = new_value;
}
private:
MarkSweepCollector* collector_;
};
七、标记-清除算法与分代垃圾回收的结合
为了提高垃圾回收的效率,Android Runtime(ART)将标记-清除算法与分代垃圾回收(Generational Garbage Collection)技术相结合。分代垃圾回收的核心思想是:将对象按生命周期分为不同的代,对不同代的对象采用不同的垃圾回收策略。
在分代垃圾回收中,堆内存通常被分为新生代(Young Generation)和老年代(Old Generation)。新生代存储新创建的对象,这些对象的生命周期通常较短;老年代存储存活时间较长的对象。
分代垃圾回收的优势在于:大多数对象的生命周期都很短,因此可以频繁地对新生代进行垃圾回收,而不需要每次都扫描整个堆内存。这样可以显著减少垃圾回收的开销,提高应用程序的性能。
在ART中,分代垃圾回收通常采用标记-清除算法的变种。对于新生代,由于对象生命周期短,垃圾回收频率高,通常采用标记-清除算法的优化版本,如标记-复制(Mark-Copy)算法;对于老年代,由于对象生命周期长,垃圾回收频率低,通常采用标记-清除或标记-整理(Mark-Compact)算法。
分代垃圾回收的核心代码如下:
c
// 分代垃圾回收器类
class GenerationalCollector : public GarbageCollector {
public:
GenerationalCollector(Heap* heap)
: GarbageCollector(heap),
young_collector_(new MarkCopyCollector(heap)),
old_collector_(new MarkSweepCollector(heap)) {}
// 执行垃圾回收
void CollectGarbage() override {
// 根据垃圾回收的触发条件选择回收新生代还是老年代
if (ShouldCollectYoungGeneration()) {
// 回收新生代
young_collector_->CollectGarbage();
} else {
// 回收老年代
old_collector_->CollectGarbage();
}
}
// 判断是否应该回收新生代
bool ShouldCollectYoungGeneration() {
// 根据新生代的使用情况和其他条件判断是否需要回收新生代
// ...
return true; // 示例:总是返回true,表示应该回收新生代
}
private:
GarbageCollector* young_collector_; // 新生代垃圾回收器
GarbageCollector* old_collector_; // 老年代垃圾回收器
};
// 标记-复制收集器类(新生代垃圾回收器的一种实现)
class MarkCopyCollector : public GarbageCollector {
public:
MarkCopyCollector(Heap* heap) : GarbageCollector(heap) {
// 初始化新生代内存区域
young_space_ = heap->GetYoungSpace();
from_space_ = young_space_->GetFromSpace();
to_space_ = young_space_->GetToSpace();
}
// 执行垃圾回收
void CollectGarbage() override {
// 暂停应用程序
SuspendAllThreads();
// 初始化标记上下文
InitializeMarkContext();
// 枚举并标记根对象
EnumerateRoots();
// 执行标记-复制操作
PerformMarkCopy();
// 交换from空间和to空间
SwapFromToSpaces();
// 恢复应用程序
ResumeAllThreads();
}
// 执行标记-复制操作
void PerformMarkCopy() {
// 设置to空间的分配指针
allocation_pointer_ = to_space_->GetStart();
// 处理标记栈,复制所有可达对象
ProcessMarkStack();
}
// 处理标记栈,复制所有可达对象
void ProcessMarkStack() {
while (!mark_stack_->IsEmpty()) {
Object* obj = mark_stack_->Pop();
// 复制对象到to空间
Object* new_obj = CopyObject(obj);
// 遍历新对象的所有引用字段
ObjectReferenceVisitor visitor(this);
new_obj->VisitReferences(visitor);
}
}
// 复制对象到to空间
Object* CopyObject(Object* obj) {
size_t obj_size = obj->GetSize();
// 检查to空间是否有足够的空间
if (allocation_pointer_ + obj_size > to_space_->GetEnd()) {
// 空间不足,需要扩展或进行其他处理
// ...
}
// 复制对象
Object* new_obj = reinterpret_cast<Object*>(allocation_pointer_);
memcpy(new_obj, obj, obj_size);
// 更新对象的内存地址
UpdateObjectAddress(obj, new_obj);
// 更新分配指针
allocation_pointer_ += obj_size;
return new_obj;
}
private:
YoungSpace* young_space_; // 新生代内存区域
Space* from_space_; // 源空间
Space* to_space_; // 目标空间
uint8_t* allocation_pointer_; // 分配指针
};
八、标记-清除算法的性能优化策略
为了提高标记-清除算法的性能,Android Runtime(ART)实现了多种优化策略。这些策略主要集中在减少垃圾回收的停顿时间、提高内存分配和回收的效率、降低内存碎片等方面。
8.1 并行垃圾回收
并行垃圾回收是指在垃圾回收过程中,使用多个线程同时执行垃圾回收任务,从而加快垃圾回收的速度,减少应用程序的停顿时间。
在ART中,标记-清除算法的标记阶段和清除阶段都可以并行执行。例如,在标记阶段,可以使用多个线程同时遍历对象图,标记可达对象;在清除阶段,可以使用多个线程同时扫描堆内存,回收未被标记的对象。
并行垃圾回收的核心代码如下:
c
// 并行标记-清除收集器类
class ParallelMarkSweepCollector : public MarkSweepCollector {
public:
ParallelMarkSweepCollector(Heap* heap, int thread_count)
: MarkSweepCollector(heap), thread_count_(thread_count) {}
// 执行垃圾回收
void CollectGarbage() override {
// 初始标记阶段
InitialMark();
// 并行标记阶段
ParallelMark();
// 重新标记阶段
Remark();
// 并行清除阶段
ParallelSweep();
}
// 并行标记阶段
void ParallelMark() {
// 创建并启动多个标记线程
MarkThread** threads = new MarkThread*[thread_count_];
for (int i = 0; i < thread_count_; ++i) {
threads[i] = new MarkThread(this, i, thread_count_);
threads[i]->Start();
}
// 等待所有标记线程完成
for (int i = 0; i < thread_count_; ++i) {
threads[i]->Join();
delete threads[i];
}
delete[] threads;
}
// 并行清除阶段
void ParallelSweep() {
// 计算每个线程负责的内存区域
size_t heap_size = heap_->GetEnd() - heap_->GetStart();
size_t chunk_size = heap_size / thread_count_;
// 创建并启动多个清除线程
SweepThread** threads = new SweepThread*[thread_count_];
for (int i = 0; i < thread_count_; ++i) {
uint8_t* start = heap_->GetStart() + i * chunk_size;
uint8_t* end = (i == thread_count_ - 1) ? heap_->GetEnd() : start + chunk_size;
threads[i] = new SweepThread(this, start, end);
threads[i]->Start();
}
// 等待所有清除线程完成
for (int i = 0; i < thread_count_; ++i) {
threads[i]->Join();
delete threads[i];
}
delete[] threads;
}
private:
int thread_count_; // 线程数量
};
8.2 增量垃圾回收
增量垃圾回收是指将垃圾回收过程分成多个小步骤,在应用程序运行的间隙执行这些小步骤,从而减少应用程序的停顿时间。
在ART中,增量垃圾回收主要通过以下方式实现:
-
增量标记:将标记阶段分成多个小步骤,每次只标记一部分对象,然后暂停标记,让应用程序继续运行一段时间,再继续标记。
-
增量清除:将清除阶段分成多个小步骤,每次只清除一部分未被标记的对象,然后暂停清除,让应用程序继续运行一段时间,再继续清除。
增量垃圾回收的核心代码如下:
c
// 增量标记-清除收集器类
class IncrementalMarkSweepCollector : public MarkSweepCollector {
public:
IncrementalMarkSweepCollector(Heap* heap) : MarkSweepCollector(heap) {}
// 执行垃圾回收
void CollectGarbage() override {
// 初始标记阶段
InitialMark();
// 增量标记阶段
while (!IsMarkingComplete()) {
// 执行一小段标记工作
IncrementalMark();
// 让应用程序运行一段时间
AllowApplicationToRun();
}
// 重新标记阶段
Remark();
// 增量清除阶段
while (!IsSweepingComplete()) {
// 执行一小段清除工作
IncrementalSweep();
// 让应用程序运行一段时间
AllowApplicationToRun();
}
}
// 执行一小段标记工作
void IncrementalMark() {
// 从标记栈中弹出一定数量的对象进行标记
for (int i = 0; i < MARK_STEP_SIZE && !mark_stack_->IsEmpty(); ++i) {
Object* obj = mark_stack_->Pop();
// 遍历对象的所有引用字段
ObjectReferenceVisitor visitor(this);
obj->VisitReferences(visitor);
}
}
// 执行一小段清除工作
void IncrementalSweep() {
// 计算本次清除的内存区域大小
size_t sweep_size = SWEEP_STEP_SIZE;
if (current_sweep_address_ + sweep_size > heap_->GetEnd()) {
sweep_size = heap_->GetEnd() - current_sweep_address_;
}
// 扫描并回收指定区域内的未被标记对象
uint8_t* end_address = current_sweep_address_ + sweep_size;
Object* obj = reinterpret_cast<Object*>(current_sweep_address_);
while (reinterpret_cast<uint8_t*>(obj) < end_address) {
// 获取对象的大小
size_t obj_size = obj->GetSize();
if (!IsMarked(obj)) {
// 对象未被标记,回收其占用的内存空间
FreeObject(obj, obj_size);
} else {
// 对象已被标记,重置其标记状态
UnmarkObject(obj);
}
// 移动到下一个对象
obj = reinterpret_cast<Object*>(reinterpret_cast<uint8_t*>(obj) + obj_size);
}
// 更新当前清除地址
current_sweep_address_ = end_address;
}
private:
static const int MARK_STEP_SIZE = 1000; // 每次标记的对象数量
static const int SWEEP_STEP_SIZE = 1024*16; // 每次清除的内存大小(字节)
uint8_t* current_sweep_address_; // 当前清除地址
};
8.3 内存碎片整理
内存碎片是标记-清除算法的一个主要问题,它会导致后续内存分配效率降低。为了解决这个问题,ART实现了多种内存碎片整理策略。
其中一种常见的策略是标记-整理(Mark-Compact)算法。该算法在标记阶段之后,会将所有存活的对象移动到堆内存的一端,然后清理掉剩余的内存空间,从而消除内存碎片。
另一种策略是在清除阶段合并相邻的空闲内存块,减少小碎片的数量。这种策略可以通过维护一个有序的空闲内存列表来实现,每次回收内存时,检查相邻的内存块是否也是空闲的,如果是,则将它们合并成一个更大的空闲内存块。
内存碎片整理的核心代码如下:
c
// 标记-整理收集器类
class MarkCompactCollector : public MarkSweepCollector {
public:
MarkCompactCollector(Heap* heap) : MarkSweepCollector(heap) {}
// 执行垃圾回收
void CollectGarbage() override {
// 标记阶段
MarkPhase();
// 整理阶段
CompactPhase();
}
// 整理阶段
void CompactPhase() {
// 计算存活对象的新位置
ComputeNewObjectPositions();
// 移动对象到新位置
MoveObjects();
// 更新所有引用
UpdateReferences();
// 重置堆内存状态
ResetHeapState();
}
// 计算存活对象的新位置
void ComputeNewObjectPositions() {
// 设置分配指针初始位置
uint8_t* allocation_pointer = heap_->GetStart();
// 遍历堆内存,计算每个存活对象的新位置
Object* obj = reinterpret_cast<Object*>(heap_->GetStart());
while (obj < reinterpret_cast<Object*>(heap_->GetEnd())) {
if (IsMarked(obj)) {
// 记录对象的新位置
obj->SetNewPosition(allocation_pointer);
// 更新分配指针
allocation_pointer += obj->GetSize();
}
// 移动到下一个对象
obj = GetNextObject(obj);
}
}
// 移动对象到新位置
void MoveObjects() {
// 遍历堆内存,移动每个存活对象
Object* obj = reinterpret_cast<Object*>(heap_->GetStart());
while (obj < reinterpret_cast<Object*>(heap_->GetEnd())) {
if (IsMarked(obj)) {
// 获取对象的新位置
uint8_t* new_position = obj->GetNewPosition();
// 移动对象
memcpy(new_position, obj, obj->GetSize());
// 更新对象的内存地址
UpdateObjectAddress(obj, reinterpret_cast<Object*>(new_position));
}
// 移动到下一个对象
obj = GetNextObject(obj);
}
}
// 更新所有引用
void UpdateReferences() {
// 遍历所有根对象,更新它们的引用
UpdateRootReferences();
// 遍历堆内存,更新所有对象的引用
Object* obj = reinterpret_cast<Object*>(heap_->GetStart());
while (obj < reinterpret_cast<Object*>(heap_->GetEnd())) {
if (IsMarked(obj)) {
// 更新对象内部的引用
UpdateObjectReferences(obj);
}
// 移动到下一个对象
obj = GetNextObject(obj);
}
}
};
九、标记-清除算法的监控与调试机制
为了帮助开发者理解和优化应用程序的内存使用,Android Runtime(ART)提供了一系列的监控与调试机制,用于监控标记-清除算法的运行状态和性能指标。
9.1 垃圾回收日志
ART提供了详细的垃圾回收日志,记录了每次垃圾回收的触发原因、执行时间、回收的内存大小等信息。通过分析这些日志,开发者可以了解应用程序的内存使用模式和垃圾回收行为,找出潜在的内存问题。
垃圾回收日志的核心代码如下:
c
// 垃圾回收日志记录类
class GcLogger {
public:
// 记录垃圾回收开始
void LogGcStart(GcCause cause, GcType type) {
if (IsLoggingEnabled()) {
LOG(INFO) << "GC " << GcTypeToString(type) << " (" << GcCauseToString(cause) << ") started";
start_time_ = GetCurrentTime();
}
}
// 记录垃圾回收结束
void LogGcEnd(GcType type, size_t freed_bytes, size_t total_bytes) {
if (IsLoggingEnabled()) {
uint64_t end_time = GetCurrentTime();
uint64_t duration = end_time - start_time_;
LOG(INFO) << "GC " << GcTypeToString(type) << " freed "
<< (freed_bytes / 1024) << "KB, "
<< "heap size is " << (total_bytes / 1024) << "KB, "
<< "duration " << duration << "ms";
}
}
// 记录标记阶段开始
void LogMarkPhaseStart() {
if (IsLoggingEnabled()) {
LOG(INFO) << "Mark phase started";
mark_start_time_ = GetCurrentTime();
}
}
// 记录标记阶段结束
void LogMarkPhaseEnd(size_t marked_objects) {
if (IsLoggingEnabled()) {
uint64_t mark_end_time = GetCurrentTime();
uint64_t mark_duration = mark_end_time - mark_start_time_;
LOG(INFO) << "Mark phase completed, marked " << marked_objects
<< " objects, duration " << mark_duration << "ms";
}
}
// 记录清除阶段开始
void LogSweepPhaseStart() {
if (IsLoggingEnabled()) {
LOG(INFO) << "Sweep phase started";
sweep_start_time_ = GetCurrentTime();
}
}
// 记录清除阶段结束
void LogSweepPhaseEnd(size_t freed_objects, size_t freed_bytes) {
if (IsLoggingEnabled()) {
uint64_t sweep_end_time = GetCurrentTime();
uint64_t sweep_duration = sweep_end_time - sweep_start_time_;
LOG(INFO) << "Sweep phase completed, freed " << freed_objects
<< " objects, " << (freed_bytes / 1024) << "KB, "
<< "duration " << sweep_duration << "ms";
}
}
private:
bool IsLoggingEnabled() {
// 检查是否启用了垃圾回收日志
return true; // 示例:总是返回true
}
uint64_t GetCurrentTime() {
// 获取当前时间(毫秒)
// ...
return 0; // 示例
}
const char* GcTypeToString(GcType type) {
// 将垃圾回收类型转换为字符串
// ...
return ""; // 示例
}
const char* GcCauseToString(GcCause cause) {
// 将垃圾回收原因转换为字符串
// ...
return ""; // 示例
}
uint64_t start_time_; // 垃圾回收开始时间
uint64_t mark_start_time_; // 标记阶段开始时间
uint64_t sweep_start_time_; // 清除阶段开始时间
};
9.2 内存分析工具
Android提供了多种内存分析工具,如Android Profiler、Heap Viewer等,这些工具可以帮助开发者分析应用程序的内存使用情况,包括对象分配、垃圾回收频率、内存泄漏等问题。
这些工具通常与标记-清除算法紧密配合,通过监控垃圾回收的执行情况和内存使用变化,帮助开发者找出内存优化的方向。
9.3 性能指标监控
ART还提供了一系列的性能指标监控接口,用于获取垃圾回收的详细性能数据,如垃圾回收频率、每次垃圾回收的耗时、回收的内存大小等。
开发者可以通过这些接口收集性能数据,分析应用程序的内存使用模式,优化应用程序的内存管理策略。
性能指标监控的核心代码如下:
c
// 垃圾回收性能指标监控类
class GcPerformanceMonitor {
public:
GcPerformanceMonitor()
: total_gc_count_(0),
total_gc_time_(0),
total_freed_bytes_(0),
max_gc_time_(0) {}
// 记录一次垃圾回收
void RecordGc(uint64_t duration, size_t freed_bytes) {
total_gc_count_++;
total_gc_time_ += duration;
total_freed_bytes_ += freed_bytes;
if (duration > max_gc_time_) {
max_gc_time_ = duration;
}
}
// 获取总垃圾回收次数
uint64_t GetTotalGcCount() const {
return total_gc_count_;
}
// 获取总垃圾回收时间(毫秒)
uint64_t GetTotalGcTime() const {
return total_gc_time_;
}
// 获取平均垃圾回收时间(毫秒)
double GetAverageGcTime() const {
return (total_gc_count_ > 0) ? (total_gc_time_ / (double)total_gc_count_) : 0;
}
// 获取最大垃圾回收时间(毫秒)
uint64_t GetMaxGcTime() const {
return max_gc_time_;
}
// 获取总共回收的内存大小(字节)
size_t GetTotalFreedBytes() const {
return total_freed_bytes_;
}
private:
uint64_t total_gc_count_; // 总垃圾回收次数
uint64_t total_gc_time_; // 总垃圾回收时间(毫秒)
size_t total_freed_bytes_; // 总共回收的内存大小(字节)
uint64_t max_gc_time_; // 最大垃圾回收时间(毫秒)
};
十、标记-清除算法在不同Android版本中的演进
随着Android系统的不断发展,标记-清除算法也在不断演进和优化。不同版本的Android系统对标记-清除算法进行了不同程度的改进,以提高垃圾回收的效率和应用程序的性能。
10.1 Android 5.0 (Lollipop) 之前
在Android 5.0之前,Dalvik虚拟机是Android的默认运行时环境。Dalvik使用的垃圾回收器基于标记-清除算法,但存在一些性能问题,如垃圾回收时应用程序停顿时间较长、内存碎片严重等。
10.2 Android 5.0 (Lollipop) - Android Runtime (ART) 的引入
Android 5.0引入了Android Runtime (ART) 作为新的默认运行时环境,取代了Dalvik虚拟机。ART的垃圾回收器相比Dalvik有了显著改进,采用了更高效的标记-清除算法,并引入了并发垃圾回收技术,大大减少了应用程序的停顿时间。
在ART中,标记-清除算法与分代垃圾回收技术相结合,将堆内存分为新生代和老年代,对不同代的对象采用不同的垃圾回收策略。同时,ART还实现了写屏障技术,确保在并发垃圾回收过程中能够正确标记所有可达对象。
10.3 Android 6.0 (Marshmallow) - 并发标记-清除的优化
Android 6.0对ART的并发标记-清除算法进行了进一步优化,提高了并发垃圾回收的效率和稳定性。具体改进包括:
-
优化了写屏障的实现,减少了写屏障带来的性能开销。
-
改进了并发标记和清除的线程调度策略,更好地平衡了垃圾回收和应用程序执行的资源分配。
-
增加了对大型对象的特殊处理,减少了大型对象分配和回收时的性能影响。
10.4 Android 7.0 (Nougat) - 增量垃圾回收的引入
Android 7.0引入了增量垃圾回收技术,将垃圾回收过程分成多个小步骤,在应用程序运行的间隙执行这些小步骤,从而进一步减少了应用程序的停顿时间。
增量垃圾回收技术特别适用于对响应性要求较高的应用程序,如游戏、视频播放等。通过将垃圾回收的停顿时间分散到多个小时间段内,用户几乎感觉不到垃圾回收的影响。
10.5 Android 8.0 (Oreo) - 并行垃圾回收的增强
Android 8.0对并行垃圾回收技术进行了增强,增加了更多的并行处理线程,提高了垃圾回收的速度。同时,还优化了并行标记和清除的算法,减少了线程间的同步开销。
这些改进使得ART在多核处理器上的性能得到了显著提升,特别是在处理大量对象分配和回收的场景下。
10.6 Android 9.0 (Pie) - 内存碎片整理的优化
Android 9.0对内存碎片整理算法进行了优化,减少了内存碎片的产生,提高了内存分配的效率。具体改进包括:
-
改进了标记-整理算法的实现,更有效地移动存活对象,减少内存碎片。
-
增加了对空闲内存块的合并策略,将相邻的空闲内存块合并成更大的块,提高内存分配的成功率。
-
优化了大型对象的分配策略,减少大型对象分配时的内存碎片。
10.7 Android 10及以后版本 - 持续优化
从Android 10开始,ART的垃圾回收器继续进行持续优化,包括进一步减少停顿时间、提高内存使用效率、优化并发处理等方面。同时,还增加了更多的监控和调试工具,帮助开发者更好地理解和优化应用程序的内存使用。
这些优化使得Android系统在处理内存密集型应用程序时更加流畅,用户体验得到了进一步提升。
以上从多个维度深入解析了Android Runtime标记-清除垃圾回收算法的核心流程原理。如果你对某个部分想进一步了解,或还有其他延伸需求,欢迎随时提出。