关于Android的垃圾回收机制,之前笔者也有发过相关的文章,但是这次增加了一部分GC源码的分析。本文的第一到第五部分,介绍GC算法的基本原理和常见优化方式。第六部分介绍Android中GC算法的部分源码实现。读者可以按需要阅读。
在Android应用开发中,内存管理和垃圾回收(GC)对于应用性能和稳定性至关重要。理解GC机制有助于我们编写更高效的代码,避免内存泄漏和内存溢出。本文将深入探讨Android GC机制的工作原理。如果对内存管理感兴趣,还可以阅读我的文章Android内存优化实战。
Android应用运行在Dalvik虚拟机(Android 4.4之前)或ART虚拟机(Android 4.4及之后)上。虚拟机负责为应用分配和管理内存。当应用需要分配内存时,虚拟机会在堆内存中分配一块空间。堆内存是应用所有线程共享的内存区域,用于存储对象和数据。
随着应用的运行,堆内存中会不断产生新的对象。当对象不再被使用时,它们占用的内存需要被回收,以便为新的对象分配空间。这就是垃圾回收的主要任务。
垃圾回收可以由以下条件触发:
System.gc()
来显式触发GC。然而,这种做法通常不推荐,因为它可能导致GC过于频繁,影响应用性能。Android操作系统使用的是Dalvik虚拟机或者ART(Android RunTime)来执行应用程序的代码。这两种虚拟机在垃圾回收(GC)算法上有所不同。
Dalvik虚拟机主要使用标记-清除(Mark-Sweep)和标记-压缩(Mark-Compact)两种GC算法。
ART在Dalvik的基础上做了很多优化,包括在垃圾回收算法上。ART主要使用分代收集(Generational Collection)和并发标记-清除(Concurrent Mark-Sweep,CMS)两种GC算法。
总的来说,Android虚拟机的GC算法是为了在保证内存使用效率的同时,尽可能减少GC对应用程序性能和用户体验的影响。
为了减少垃圾回收对应用性能的影响,我们可以采取以下措施:
监控GC(Garbage Collection)耗时情况是一个重要的性能优化手段。GC过程会暂停应用的运行,因此,频繁的GC会影响应用的启动速度和响应速度。以下是一些常用的监控方法:
adb shell dumpsys meminfo <package_name>
命令来获取应用的内存使用情况,其中包括GC的次数和总耗时。我们还可以使用adb logcat -s GC命令来获取GC的详细日志。Debug.startMethodTracing()
和Debug.stopMethodTracing()
方法来开启和关闭方法追踪,然后使用Debug.getNativeHeapAllocatedSize()
方法来获取已分配的内存大小。通过比较两次调用Debug.getNativeHeapAllocatedSize()
方法的结果,我们可以估计GC的耗时。以上方法可以帮助我们监控Android中的GC耗时情况。通过监控,我们可以找出频繁GC的原因,如内存泄漏,过度分配等,并进行相应的优化,从而提高应用的启动速度和响应速度。
HeapTask是Android中用于处理内存相关任务的基础类,它位于art/runtime/gc/task_processor.h
。HeapTask的主要作用是对内存进行垃圾回收、内存整理以及内存压缩等操作,以便在应用程序运行时释放不再使用的内存。HeapTask的实现基于Android运行时的垃圾回收器(Garbage Collector,GC)。
HeapTask的主要原理是通过标记-清除(Mark-Sweep)算法来识别和回收不再使用的内存。在标记阶段,垃圾回收器会从根对象开始,遍历所有可达对象,并将它们标记为存活。在清除阶段,垃圾回收器会回收所有未标记的对象所占用的内存。
在Android源码中,HeapTask类的定义如下:
// HeapTask类,继承自SelfDeletingTask
class HeapTask : public SelfDeletingTask {
public:
// 构造函数,需要传入一个目标运行时间(纳秒)
explicit HeapTask(uint64_t target_run_time) : target_run_time_(target_run_time) {
}
// 获取目标运行时间的方法
uint64_t GetTargetRunTime() const {
return target_run_time_;
}
private:
// 更新目标运行时间,任务处理器会在任务弹出时重新插入任务,并更新目标运行时间
void SetTargetRunTime(uint64_t new_target_run_time) {
target_run_time_ = new_target_run_time;
}
// 目标运行时间(纳秒)
uint64_t target_run_time_;
// 声明TaskProcessor为友元类,以便访问私有成员
friend class TaskProcessor;
// 禁止使用隐式构造函数
DISALLOW_IMPLICIT_CONSTRUCTORS(HeapTask);
};
HeapTask
类是一个继承自 SelfDeletingTask
的任务类,用于表示堆任务。它包含一个目标运行时间(target_run_time_
),以纳秒为单位。在构造函数中,需要传入目标运行时间。类中还提供了获取和设置目标运行时间的方法。此外,TaskProcessor
类被声明为友元类,以便访问 HeapTask
的私有成员。最后,禁止使用隐式构造函数。
以下是结合源码对 Android 中每个 HeapTask
的作用的详细解释:
ConcurrentGCTask
当 Java 内存达到阈值时,会执行此任务。它用于执行并发垃圾回收(GC),以在后台线程中清理不再使用的内存。这种机制可以在不阻塞应用程序主线程的情况下,有效地回收内存。
// Heap::ConcurrentGCTask类,继承自HeapTask,表示并发垃圾回收任务
class Heap::ConcurrentGCTask : public HeapTask {
public:
// 构造函数,接收目标运行时间、GC原因、是否强制执行全量回收以及GC序列号
ConcurrentGCTask(uint64_t target_time, GcCause cause, bool force_full, uint32_t gc_num)
: HeapTask(target_time), cause_(cause), force_full_(force_full), my_gc_num_(gc_num) {}
// 重写Run方法,执行并发GC任务
void Run(Thread* self) override {
// 获取当前运行时和堆
Runtime* runtime = Runtime::Current();
gc::Heap* heap = runtime->GetHeap();
// 检查GC序列号是否小于当前GC序列号 + 2
DCHECK(GCNumberLt(my_gc_num_, heap->GetCurrentGcNum() + 2)); // <= current_gc_num + 1
// 执行并发GC
heap->ConcurrentGC(self, cause_, force_full_, my_gc_num_);
// 检查当前GC序列号是否小于my_gc_num_,如果是,则运行时应该处于关闭状态
CHECK_IMPLIES(GCNumberLt(heap->GetCurrentGcNum(), my_gc_num_), runtime->IsShuttingDown(self));
}
private:
const GcCause cause_; // GC原因
const bool force_full_; // 如果为true,则强制执行全量(或部分)回收
const uint32_t my_gc_num_; // 请求的GC序列号
};
Heap::ConcurrentGCTask
类是一个继承自 HeapTask
的任务类,用于表示并发垃圾回收任务。在构造函数中,需要传入目标运行时间、GC 原因、是否强制执行全量回收以及 GC 序列号。
Run
方法是执行并发 GC 任务的核心逻辑。首先,获取当前运行时和堆,然后检查 GC 序列号是否小于当前 GC 序列号 + 2。接着,调用 heap->ConcurrentGC()
方法执行并发 GC。最后,检查当前 GC 序列号是否小于 my_gc_num_
,如果是,则运行时应该处于关闭状态。
CollectorTransitionTask
当应用程序从前台切换到后台时,会执行此任务。它用于切换 GC 类型,例如在后台时,可能会切换到拷贝回收这种 GC 机制。这有助于在不影响用户体验的情况下,更高效地回收内存。
// Heap::CollectorTransitionTask类,继承自HeapTask,表示收集器切换任务
class Heap::CollectorTransitionTask : public HeapTask {
public:
// 构造函数,接收目标运行时间
explicit CollectorTransitionTask(uint64_t target_time) : HeapTask(target_time) {}
// 重写Run方法,执行收集器切换任务
void Run(Thread* self) override {
// 获取当前运行时的堆
gc::Heap* heap = Runtime::Current()->GetHeap();
// 执行待处理的收集器切换
heap->DoPendingCollectorTransition();
// 清除待处理的收集器切换
heap->ClearPendingCollectorTransition(self);
}
};
Heap::CollectorTransitionTask
类是一个继承自 HeapTask
的任务类,用于表示收集器切换任务。在构造函数中,需要传入目标运行时间。
Run
方法是执行收集器切换任务的核心逻辑。首先,获取当前运行时的堆。接着,调用 heap->DoPendingCollectorTransition()
方法执行待处理的收集器切换。最后,调用 heap->ClearPendingCollectorTransition(self)
方法清除待处理的收集器切换。这个任务主要用于在应用程序前后台切换时,根据不同的场景切换合适的垃圾回收器。
HeapTrimTask
GC 完成后,如果需要将堆中空闲的内存归还给内核,则会执行此任务。这样可以减少应用程序占用的内存,从而提高系统的整体性能。
// Heap::HeapTrimTask类,继承自HeapTask,表示堆内存整理任务
class Heap::HeapTrimTask : public HeapTask {
public:
// 构造函数,接收一个增量时间(delta_time),计算出目标运行时间
explicit HeapTrimTask(uint64_t delta_time) : HeapTask(NanoTime() + delta_time) { }
// 重写Run方法,执行堆内存整理任务
void Run(Thread* self) override {
// 获取当前运行时的堆
gc::Heap* heap = Runtime::Current()->GetHeap();
// 整理堆内存
heap->Trim(self);
// 清除待处理的内存整理任务
heap->ClearPendingTrim(self);
}
};
Heap::HeapTrimTask
类是一个继承自 HeapTask
的任务类,用于表示堆内存整理任务。在构造函数中,需要传入一个增量时间(delta_time
),然后计算出目标运行时间。
Run
方法是执行堆内存整理任务的核心逻辑。首先,获取当前运行时的堆。接着,调用 heap->Trim(self)
方法整理堆内存。整理堆内存的主要目的是将堆中空闲的内存归还给内核,从而减少应用程序占用的内存,提高系统的整体性能。最后,调用 heap->ClearPendingTrim(self)
方法清除待处理的内存整理任务。
TriggerPostForkCCGcTask
从 Android 8 开始,在启动时为了避免 GC 操作,系统会执行此任务,将 HeapTaskDaemon
线程阻塞 2 秒。这可以减少启动过程中的 GC 开销,从而提高应用程序的启动速度。
// Run a gc if we haven't run one since initial_gc_num. This forces processes to
// reclaim memory allocated during startup, even if they don't do much
// allocation post startup. If the process is actively allocating and triggering
// GCs, or has moved to the background and hence forced a GC, this does nothing.
class Heap::TriggerPostForkCCGcTask : public HeapTask {
public:
// 构造函数,接收目标运行时间和初始GC序列号
explicit TriggerPostForkCCGcTask(uint64_t target_time, uint32_t initial_gc_num) :
HeapTask(target_time), initial_gc_num_(initial_gc_num) {}
// 重写Run方法,执行启动后的GC任务
void Run(Thread* self) override {
// 获取当前运行时的堆
gc::Heap* heap = Runtime::Current()->GetHeap();
// 如果当前GC序列号等于初始GC序列号,说明没有进行过GC
if (heap->GetCurrentGcNum() == initial_gc_num_) {
// 如果启用了日志记录所有GC操作
if (kLogAllGCs) {
LOG(INFO) << "Forcing GC for allocation-inactive process";
}
// 请求执行并发GC
heap->RequestConcurrentGC(self, kGcCauseBackground, false, initial_gc_num_);
}
}
private:
uint32_t initial_gc_num_; // 初始GC序列号
};
Heap::TriggerPostForkCCGcTask
类是一个继承自 HeapTask
的任务类,用于在应用启动后触发一次 GC 任务。在构造函数中,需要传入目标运行时间和初始 GC 序列号。
Run
方法是执行启动后的 GC 任务的核心逻辑。首先,获取当前运行时的堆。接着,检查当前 GC 序列号是否等于初始 GC 序列号。如果等于,说明没有进行过 GC,此时需要强制执行一次 GC 任务,以回收启动期间分配的内存。最后,调用 heap->RequestConcurrentGC(self, kGcCauseBackground, false, initial_gc_num_)
方法请求执行并发 GC。这个任务主要用于在应用启动后回收启动期间分配的内存,提高系统的整体性能。
ReduceTargetFootprintTask
与 TriggerPostForkCCGcTask
配合使用,用于在启动时减少 GC 的开销。
// Heap::ReduceTargetFootprintTask类,继承自HeapTask,表示减小目标内存占用任务
class Heap::ReduceTargetFootprintTask : public HeapTask {
public:
// 构造函数,接收目标运行时间、新的目标内存占用大小和初始GC序列号
explicit ReduceTargetFootprintTask(uint64_t target_time, size_t new_target_sz,
uint32_t initial_gc_num) :
HeapTask(target_time), new_target_sz_(new_target_sz), initial_gc_num_(initial_gc_num) {}
// 重写Run方法,执行减小目标内存占用任务
void Run(Thread* self) override {
gc::Heap* heap = Runtime::Current()->GetHeap();
MutexLock mu(self, *(heap->gc_complete_lock_));
// 如果自初始GC序列号以来没有进行过GC,并且没有正在运行的收集器
if (heap->GetCurrentGcNum() == initial_gc_num_
&& heap->collector_type_running_ == kCollectorTypeNone) {
// 获取当前目标内存占用
size_t target_footprint = heap->target_footprint_.load(std::memory_order_relaxed);
// 如果当前目标内存占用大于新的目标内存占用
if (target_footprint > new_target_sz_) {
// 使用CompareAndSetStrongRelaxed原子操作更新目标内存占用
if (heap->target_footprint_.CompareAndSetStrongRelaxed(target_footprint, new_target_sz_)) {
// 设置默认的并发启动字节
heap->SetDefaultConcurrentStartBytesLocked();
}
}
}
}
private:
size_t new_target_sz_; // 新的目标内存占用大小
uint32_t initial_gc_num_; // 初始GC序列号
};
Heap::ReduceTargetFootprintTask
类是一个继承自 HeapTask
的任务类,用于表示减小目标内存占用任务。在构造函数中,需要传入目标运行时间、新的目标内存占用大小和初始 GC 序列号。
Run
方法是执行减小目标内存占用任务的核心逻辑。首先,获取当前运行时的堆。接着,检查自初始 GC 序列号以来是否进行过 GC,并且没有正在运行的收集器。如果满足条件,则获取当前目标内存占用,如果当前目标内存占用大于新的目标内存占用,使用原子操作更新目标内存占用,并设置默认的并发启动字节。这个任务主要用于在应用启动后减少内存占用,提高系统的整体性能。
ClearedReferenceTask
在对象回收时,会执行此任务。任务中调用 Java 层的 ReferenceQueue.add
方法,将被回收对象引用添加到 ReferenceQueue
队列中。LeakCanary 就是通过这个队列来判断内存泄漏的。
// ClearedReferenceTask类,继承自HeapTask,表示清除引用任务
class ClearedReferenceTask : public HeapTask {
public:
// 构造函数,接收一个清除引用的全局引用
explicit ClearedReferenceTask(jobject cleared_references)
: HeapTask(NanoTime()), cleared_references_(cleared_references) {
}
// 重写Run方法,执行清除引用任务
void Run(Thread* thread) override {
// 获取当前线程的对象访问
ScopedObjectAccess soa(thread);
// 调用Java层的ReferenceQueue.add方法,将被回收对象引用添加到ReferenceQueue队列中
WellKnownClasses::java_lang_ref_ReferenceQueue_add->InvokeStatic<'V', 'L'>(
thread, soa.Decode<mirror::Object>(cleared_references_));
// 删除全局引用
soa.Env()->DeleteGlobalRef(cleared_references_);
}
private:
const jobject cleared_references_; // 清除引用的全局引用
};
ClearedReferenceTask
类是一个继承自 HeapTask
的任务类,用于表示清除引用任务。在构造函数中,需要传入一个清除引用的全局引用。
Run
方法是执行清除引用任务的核心逻辑。首先,获取当前线程的对象访问(ScopedObjectAccess
)。接着,调用 Java 层的 ReferenceQueue.add
方法,将被回收对象引用添加到 ReferenceQueue
队列中。最后,删除全局引用。这个任务主要用于在对象回收时,将被回收对象引用添加到 ReferenceQueue
队列中。这有助于检测内存泄漏,例如 LeakCanary 就是通过这个队列来判断内存泄漏的。
这些 HeapTask
任务在 Android 系统中的内存管理中起到了关键作用,它们共同确保了应用程序在运行过程中能够高效地回收内存,提高系统性能,同时避免内存泄漏。
TaskProcessor
:HeapTask如何被执行在 Android GC(垃圾回收)中,TaskProcessor
负责处理和执行堆任务,如垃圾回收、内存整理等。以下是 TaskProcessor
在 Android GC 中的作用、原理和调用链。
TaskProcessor
的作用TaskProcessor
主要用于处理和执行堆任务,如垃圾回收、内存整理等。TaskProcessor
原理TaskProcessor
类维护了一个任务队列 tasks_
,用于存储待处理的堆任务。任务队列按照任务的目标运行时间排序,确保按照任务的优先级执行。AddTask
方法用于将任务添加到任务队列中,并通知堆任务守护线程(HeapTaskDaemon
)有新任务到来。GetTask
方法用于从任务队列中获取一个任务。它会根据任务的目标运行时间判断是否可以执行任务,如果可以执行,则从任务队列中移除任务并返回以便执行;否则,等待直到达到任务的目标运行时间。HeapTaskDaemon
)负责调用 TaskProcessor
的 GetTask
方法获取任务,并执行任务。当任务队列为空时,守护线程会等待任务队列中有新任务到来。通过 TaskProcessor
,Android GC 可以将堆任务按照优先级排序并执行,从而实现垃圾回收、内存整理等功能。
我们看一下GetTask
的实现:
HeapTask* TaskProcessor::GetTask(Thread* self) {
// 更改线程状态为kWaitingForTaskProcessor
ScopedThreadStateChange tsc(self, ThreadState::kWaitingForTaskProcessor);
// 获取锁
MutexLock mu(self, lock_);
while (true) {
// 如果任务队列为空
if (tasks_.empty()) {
// 如果处理器已停止运行,则返回nullptr
if (!is_running_) {
return nullptr;
}
// 等待任务队列中有新任务到来
cond_.Wait(self);
} else {
// 任务队列非空,查看队首任务,判断是否可以执行
const uint64_t current_time = NanoTime();
HeapTask* task = *tasks_.begin();
// 获取任务的目标运行时间
uint64_t target_time = task->GetTargetRunTime();
// 如果处理器已停止运行,或者当前时间已经超过任务的目标运行时间,则执行任务
if (!is_running_ || target_time <= current_time) {
// 从任务队列中移除任务
tasks_.erase(tasks_.begin());
// 返回任务以便执行
return task;
}
DCHECK_GT(target_time, current_time);
// 计算等待时间
const uint64_t delta_time = target_time - current_time;
const uint64_t ms_delta = NsToMs(delta_time);
const uint64_t ns_delta = delta_time - MsToNs(ms_delta);
// 等待直到达到任务的目标运行时间
cond_.TimedWait(self, static_cast<int64_t>(ms_delta), static_cast<int32_t>(ns_delta));
}
}
UNREACHABLE();
}
TaskProcessor::GetTask()
方法用于从任务处理器中获取一个任务。首先,更改线程状态为 kWaitingForTaskProcessor
并获取锁。接着,判断任务队列是否为空,如果为空,则等待任务队列中有新任务到来;如果非空,则查看队首任务,判断是否可以执行。如果任务处理器已停止运行,或者当前时间已经超过任务的目标运行时间,则从任务队列中移除任务并返回任务以便执行;否则,等待直到达到任务的目标运行时间。这个方法主要用于从任务处理器中获取一个任务,等待任务到来并根据任务的目标运行时间判断是否可以执行。
TaskProcessor
调用链TaskProcessor
类在 art/runtime/gc/task_processor.h
中定义,包含了一个任务队列 tasks_
,用于存储待处理的堆任务。art/runtime/gc/heap.cc
中,Heap
类的构造函数会创建一个 TaskProcessor
实例,用于执行垃圾回收任务。Heap
类的各种 GC 请求方法(如 RequestConcurrentGC
、RequestGC
等)中,会创建相应的垃圾回收任务(如 ConcurrentGCTask
、GcTask
等),并通过 TaskProcessor
实例的 AddTask
方法将任务添加到任务队列中。HeapTaskDaemon
类(位于 art/runtime/gc/heap_task_daemon.cc
)中,runInternal
方法会调用 TaskProcessor
的 GetTask
方法获取一个任务,并执行该任务。理解Android垃圾回收机制有助于我们编写更高效的代码,提高应用性能。通过减少对象创建、使用弱引用和软引用、避免内存泄漏以及优化数据结构,我们可以降低垃圾回收的频率和开销,从而提高应用的响应速度和稳定性。