Fundamentals 33 min read

How V8 Optimizes Object Properties: From TaggedImpl to Slack Tracking

This article explains V8's internal object representation, pointer tagging, the three property storage modes (inobject, fast, slow), how constructors and literals allocate space, the rules that trigger mode switches, and the slack‑tracking technique that refines memory usage.

NetEase Cloud Music Tech Team
NetEase Cloud Music Tech Team
NetEase Cloud Music Tech Team
How V8 Optimizes Object Properties: From TaggedImpl to Slack Tracking

Object Representation in V8

All heap‑managed entities in V8 inherit from TaggedImpl. V8 uses a precise garbage collector and pointer tagging to distinguish small integers (Smi) from heap objects: the least‑significant bit of an address is 0 for Smis and 1 for heap objects.

Object Layout and Field Offsets

The runtime memory layout consists of a native stack, the GC heap, and three pointers stored in the object header: map, propertiesOrHash, and elements. The inheritance chain is:

Object → HeapObject → JSReceiver → JSObject

Each subclass adds its own offset fields ( kMapOffset, kPropertiesOrHashOffset, kElementsOffset). Offsets are defined in field‑offsets‑tq.h and are calculated from the header size and kTaggedSize (8 bytes on 64‑bit platforms).

Property Storage Modes

Named properties can be stored in three mutually exclusive forms:

inobject – the value pointer resides directly in the object's contiguous memory; this is the fastest access path.

fast – the value is kept in a PropertyArray; an extra indirection via the propertiesOrHash pointer is required.

slow – the property is stored in a generic NameDictionary; inline caches cannot be used, making it the slowest.

Lookup Process

Search the object's DescriptorArray for the property name to obtain an index.

Combine the object's base address with the index (and the appropriate pointer) to retrieve the value.

Object Creation

From Constructors

When a constructor is compiled, V8 estimates the number of properties ( estimated property count ) and adds a fixed slack of eight slots. The initial map stores expected_nof_properties. Example:

function Ctor1() { this.p1 = 1; this.p2 = 2; }
const o1 = new Ctor1();
%DebugPrint(o1);

The debug output shows a map with inobject properties: 10 (2 estimated + 8 slack). Empty constructors receive the same default of ten inobject slots.

From Object Literals

Object literals allocate exactly the number of properties present in the literal; no slack is added. An empty literal receives a hard‑coded initial count of four slots, derived from kInitialGlobalObjectUnusedPropertiesCount. Using Object.create(null) creates a slow object directly.

Mode Transitions

If inobject space is sufficient, new properties stay inobject.

When the inobject quota is exceeded, properties move to fast.

If the fast quota (soft limit 12, hard limit 15) is exceeded, the whole object switches to slow.

Deleting any non‑last property or assigning the object as another function's prototype forces a transition to slow.

After a slow transition the object rarely returns to fast, except for rare internal migrations.

Slack Tracking

Slack tracking refines the initially over‑allocated inobject space. Each constructor has a construction_counter starting at seven. After the counter reaches zero, V8 recomputes the final instance_size based on the actual number of observed properties, reducing wasted memory.

Enabling Native Debug APIs

Running V8 with --allow-natives-syntax enables private APIs such as %DebugPrint, which prints an object's map, instance size, and property layout. Example command:

node --allow-natives-syntax test.js

In‑Depth Example of Constructor‑Based Creation

Consider the following constructors:

function Ctor1() { this.p1 = 1; this.p2 = 2; }
function Ctor2(condition) {
  this.p1 = 1;
  this.p2 = 2;
  if (condition) { this.p3 = 3; this.p4 = 4; }
}
const o1 = new Ctor1();
const o2 = new Ctor2(true);
%DebugPrint(o1);
%DebugPrint(o2);

The debug output for Ctor1 shows inobject properties: 10 (2 real properties + 8 slack). For Ctor2 the estimated count is 4, so the map records inobject properties: 12 (4 + 8). If the constructor adds more properties at runtime, they will first occupy the pre‑allocated slack; once the slack is exhausted the object transitions to fast.

Empty Constructors

Even an empty constructor gets inobject properties: 10. V8 assumes most constructors will have at least two properties, so it adds a default of two before the eight‑slot slack.

Classes

ES6 classes are syntactic sugar for constructor functions. The same inobject estimation applies. For example:

class C { p1 = 1; p2 = 2; p3 = 3; }
const o = new C();
%DebugPrint(o);

The map reports inobject properties: 11 (3 declared + 8 slack). When class fields are compiled with Object.defineProperty (as produced by Babel or TypeScript with useDefineForClassFields), the engine may not count them in the estimated property count, causing some fields to fall back to fast storage.

Object Literals

Literal creation uses the exact property count. Example:

const a = { p1: 1 };
%DebugPrint(a);

The debug output shows inobject properties: 1. An empty literal ( {}) receives four pre‑allocated slots because Genesis::CreateObjectFunction sets kInitialGlobalObjectUnusedPropertiesCount = 4. Creating an object with Object.create(null) bypasses the fast path and creates a slow object directly.

Inobject, Fast, and Slow Limits

On 64‑bit V8 without pointer compression, the maximum number of inobject properties is calculated as:

constexpr int kSystemPointerSize = sizeof(void*);
constexpr int kTaggedSize = kSystemPointerSize;
constexpr int kJSObjectHeaderSize = 3 * kTaggedSize;
constexpr int kMaxInstanceSize = 255 * kTaggedSize;
constexpr int kMaxInObjectProperties = (kMaxInstanceSize - kJSObjectHeaderSize) >> 3; // 252

Fast properties are stored in a PropertyArray that grows in steps of kFieldsAdded = 3. The soft limit is 12, the hard limit is 15 (checked by Map::TooManyFastProperties).

Practical Tests

Adding properties to a plain object demonstrates the transitions:

const obj = {};
for (let i = 0; i < 19; ++i) obj['p' + i] = 1;
%DebugPrint(obj);

With 4 properties the object uses FixedArray[0] (inobject). At 19 properties the map shows a PropertyArray[15] (fast). At 20 properties the map switches to a NameDictionary[101] (slow).

Summary of Key Points

V8 objects have three storage modes: inobject (fastest), fast , and slow (slowest).

Constructor‑based creation allocates estimated properties + 8 slack inobject slots (up to 252 total).

Object literals allocate exactly the literal's property count (four slots for an empty literal).

When inobject space is exhausted, properties move to fast; exceeding fast limits (soft 12, hard 15) converts the whole object to slow.

Deleting non‑last properties or using an object as another function's prototype forces a slow transition.

Slack tracking reduces over‑allocation by adjusting instance_size after a constructor has been invoked seven times.

Using --allow-natives-syntax and %DebugPrint is a convenient way to inspect maps, offsets, and property storage during experimentation.

References

A tour of V8: object representation – https://www.jayconrod.com/posts/52/a-tour-of-v8--object-representation

Fast properties in V8 – https://v8.dev/blog/fast-properties

Pointer compression in V8 – https://v8.dev/blog/pointer-compression

Slack tracking in V8 – https://v8.dev/blog/slack-tracking

Inline caching – https://en.wikipedia.org/wiki/Inline_caching

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Garbage CollectionV8JavaScript EngineObject RepresentationProperty OptimizationSlack Tracking
NetEase Cloud Music Tech Team
Written by

NetEase Cloud Music Tech Team

Official account of NetEase Cloud Music Tech Team

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.