Unifying Chromium Garbage Collection with Oilpan and cppgc
This article explores how the Chromium browser unified memory
management between its C++ DOM nodes and V8 JavaScript objects by
migrating the Oilpan garbage collector into the standalone
cppgc library. By integrating cppgc directly
into the V8 engine, Chromium resolved complex issues with cross-language
reference cycles, eliminated redundant API tracing boundaries, and
centralized garbage collection scheduling to improve overall browser
performance and memory efficiency.
The Historical Divide Between Blink and V8
Chromium’s architecture relies on two main components that constantly interact: the Blink rendering engine (written in C++) and the V8 JavaScript engine. Web applications frequently create scenarios where JavaScript objects wrap C++ DOM nodes, and those C++ DOM nodes hold references back to JavaScript event listeners or callbacks.
Historically, V8 used its own garbage collector for JavaScript objects, while Blink used a separate C++ garbage collector called Oilpan for DOM nodes. Because neither collector had full visibility into the other's memory space, reference cycles spanning both engines easily resulted in memory leaks or premature object destruction.
The Bottleneck of Cross-Component Tracing
Before the creation of cppgc, Chromium attempted to
solve cross-language reference cycles using a mechanism called
cross-component tracing. When a garbage collection cycle began, V8 and
Oilpan had to communicate across a strict API boundary.
If V8 found a reference to a C++ DOM node, it passed that pointer across the boundary to Oilpan. Oilpan would trace its C++ object graph, and if it found a reference back to a JavaScript object, it passed the pointer back to V8. This "ping-pong" communication created massive performance overhead, duplicated code, and complicated concurrent marking.
Migrating Oilpan to the cppgc Library
To eliminate the boundary between Blink and V8, Chromium engineers
extracted the core mechanics of Oilpan out of the Blink rendering engine
and refactored it into a generic C++ garbage collection library named
cppgc. Crucially, this library was moved directly into the
V8 codebase.
By embedding cppgc inside V8, the JavaScript engine
gained native ownership and understanding of the C++ heap, transforming
two separate garbage collectors into a single, unified system.
How cppgc Unified Garbage Collection
Converting Oilpan to cppgc unified garbage collection
between C++ DOM nodes and JavaScript objects in four specific ways:
- Boundary-Free Object Tracing: Because
cppgcresides inside V8, the garbage collector no longer needs to cross an external API boundary to trace references between JS and C++. V8's marker can seamlessly traverse from a JavaScript wrapper object directly into a C++ DOM node and back within the same marking loop. - Unified Worklists and Concurrent Marking: Both JavaScript objects and C++ DOM nodes now share the same underlying infrastructure for concurrent marking. Background threads managed by V8 process both C++ and JS pointers simultaneously using shared marking worklists, maximizing CPU cache locality and thread utilization.
- Centralized GC Scheduling: Previously, Blink and V8
had to guess when to trigger garbage collection based on isolated views
of memory consumption. With
cppgc, V8 has a holistic view of total memory pressure across both the JS heap and the C++ DOM heap. It can schedule garbage collection sweeps at the most optimal times for the entire browser tab. - Unified Write Barriers: To track changes to objects
while the garbage collector is running concurrently in the background,
engines use "write barriers." The
cppgcintegration allowed V8 to unify the write barrier mechanics, meaning modifications to a C++ DOM node and a JavaScript object are intercepted and recorded using the exact same logic.
The Impact on Chromium
By consolidating Oilpan into V8 via cppgc, Chromium
eliminated the synchronization overhead between its rendering and
JavaScript engines. The browser now resolves cyclic references between
C++ and JavaScript natively, uses fewer CPU cycles during garbage
collection pauses, and reclaims memory much faster, leading to a
smoother user experience on complex web applications.