Suppressing GC Execution in Android ART: Understanding HeapTaskDaemon and Hook Techniques
This article explains how the HeapTaskDaemon thread drives garbage collection in Android's ART runtime, analyzes its source code and related HeapTask classes, and presents two practical methods—adding custom HeapTasks or pausing existing ones—using ELF symbol lookup, ndk_dlopen, and virtual‑function hooking to temporarily suppress GC for better performance.
Efficient CPU usage is crucial for Android performance, and the HeapTaskDaemon thread often consumes significant CPU time while performing garbage‑collection (GC) operations, which can cause noticeable jank during app startup, page opening, or list scrolling.
The HeapTaskDaemon thread originates from the Java‑level Daemons.java object; it extends the Daemon class, which implements Runnable and creates an internal system daemon thread named HeapTaskDaemon . Its runInternal method repeatedly calls VMRuntime.getRuntime().runHeapTasks() , which ultimately invokes the native RunAllTasks function in task_processor.cc .
private static class HeapTaskDaemon extends Daemon {
private static final HeapTaskDaemon INSTANCE = new HeapTaskDaemon();
HeapTaskDaemon() { super("HeapTaskDaemon"); }
public void runInternal() {
// ...
VMRuntime.getRuntime().runHeapTasks();
}
}
private static abstract class Daemon implements Runnable {
private Thread thread;
private String name;
protected Daemon(String name) { this.name = name; }
public synchronized void start() { startInternal(); }
public void startInternal() {
if (thread != null) throw new IllegalStateException("already running");
thread = new Thread(ThreadGroup.systemThreadGroup, this, name);
thread.setDaemon(true);
thread.setSystemDaemon(true);
thread.start();
}
public final void run() { try { runInternal(); } catch (Throwable ex) { throw ex; } }
public abstract void runInternal();
}HeapTaskDaemon processes a queue of HeapTask objects. Each HeapTask inherits from SelfDeletingTask , Task , and Closure , providing virtual Run and Finalize methods. The task processor continuously extracts tasks, executes Run , then Finalize , sleeping when the queue is empty.
void TaskProcessor::RunAllTasks(Thread* self) {
while (true) {
HeapTask* task = GetTask(self);
if (task != nullptr) {
task->Run(self);
task->Finalize();
} else if (!IsRunning()) {
break;
}
}
}Two practical ways to suppress GC are proposed:
Add a custom HeapTask that sleeps, thereby blocking the daemon thread.
Locate an existing system HeapTask (e.g., ConcurrentGCTask ) and make it sleep.
Both approaches require finding the target function’s address in the libart.so library. By copying libart.so from the device and using readelf , the ELF .symtab section can be parsed to locate symbols such as _ZN3art2gc4Heap16ConcurrentGCTask3RunEPNS_6ThreadE (the Run method of ConcurrentGCTask ) and the virtual‑function table symbol _ZTVN3art2gc4Heap16ConcurrentGCTaskE .
// Example of symbol lookup in ELF
unsigned long symbolAddr;
Elf_Ehdr *header = (Elf_Ehdr *)so_addr;
Elf_Shdr *seg_table = (Elf_Shdr *)(so_addr + header->e_shoff);
for (int i = 0; i < header->e_shnum; ++i) {
if (seg_table->sh_type == SHT_SYMTAB) {
symbolAddr = seg_table->sh_offset + so_addr;
break;
}
seg_table = (Elf_Shdr *)((char*)seg_table + header->e_shentsize);
}Using the open‑source ndk_dlopen and ndk_dlsym helpers, the address of the target Run function can be obtained, and the virtual‑function table entry can be replaced with a custom hook that sleeps for a few seconds before delegating to the original implementation.
// Hook implementation (simplified)
void hookRun(void *thread) {
sleep(2); // suppress GC for 2 seconds
replaceFunc(mSlot, originalRun);
((void(*)(void*))originalRun)(thread);
}By modifying the virtual‑function table entry of ConcurrentGCTask (or any other HeapTask), the GC work can be delayed during critical UI moments without causing long‑term memory pressure, as the suppression is brief and Android 8+ already applies a similar delay on app launch.
The article concludes that mastering these techniques expands optimization possibilities not only for GC but also for other ART threads such as the JIT thread pool, and that the same principles are valuable in reverse‑engineering, security, and cheat‑engine development.
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.