docs/heap/handles.md
Handles are the primary mechanism in V8 for referencing heap objects in C++ code in a way that is safe for garbage collection. Because V8's garbage collector moves objects during compaction, raw C++ pointers to heap objects would become invalid. Handles solve this by adding a layer of indirection or by collaborating with conservative stack scanning.
This document focuses on internal handles used within the V8 implementation. For external handles used by embedders, see the public API (e.g., v8::Local and v8::Persistent).
V8 provides two main types of internal handles: Indirect Handles (Handle<T> or explicitly IndirectHandle<T>) and Direct Handles (DirectHandle<T>).
Handle<T>)Indirect handles are the traditional handle type in V8.
Handle<T> is a pointer to a location (usually in a HandleScope) that contains the actual tagged pointer to the HeapObject.HandleScope slot. All Handle instances pointing to that slot automatically see the new location.DirectHandle<T>)Direct handles are an optimization intended to reduce the overhead of indirection.
V8_ENABLE_DIRECT_HANDLE is enabled: A DirectHandle<T> is a direct wrapper around a tagged pointer to a heap object or Smi. It does NOT use a slot in a HandleScope.V8_ENABLE_DIRECT_HANDLE is NOT enabled: It is a wrapper around an IndirectHandle<T>, serving as a fallback. (Note: DirectHandle<T> is often used interchangeably with Handle<T> in API signatures that accept both).HandleScope)Local handles are managed in the context of a HandleScope.
HandleScope accumulates slots for handles created within its scope. When the HandleScope is destroyed (typically at the end of a C++ function scope), all handles created within it become invalid because their slots are reclaimed. This prevents memory leaks from handles accumulating indefinitely.HandleScope slots, they are still conceptually local. They must only live on the stack and are valid as long as the object they point to is kept alive by some other means (e.g., an indirect handle in a scope) or if they are tracked by CSS.HandleScope instances are considered roots by the GC. The GC walks all active HandleScope slots during the main thread pause to update pointers to moved objects.For objects that need to live beyond the scope of a function or a single turn, V8 provides global handles.
GlobalHandles)EternalHandles)V8 also provides MaybeHandle<T> and MaybeDirectHandle<T>.
std::optional or a nullable pointer, but specifically for handles.V8 is in the process of migrating to DirectHandle where appropriate to improve performance.
HandleScope.DirectHandle for local variables on the stack. Code should be written to use DirectHandle where possible, as it will fallback to Handle if direct handles are not enabled.