Learning Book

Память: стек, куча, объекты

Stack vs Heap

V8 использует два типа памяти:

Стек (Stack) — быстрая, структурированная память:

  • Примитивы (number, boolean, string ссылки)
  • Локальные переменные функций (если не захвачены замыканием)
  • Адреса возврата, аргументы

Куча (Heap) — большая, неструктурированная память:

  • Объекты, массивы, функции
  • Строки (содержимое)
  • Переменные, захваченные замыканиями (Context)
function example() {
  let x = 42          // стек (примитив, не захвачен замыканием)
  let obj = { a: 1 }  // ссылка на стеке, объект в куче
  let arr = [1, 2, 3] // ссылка на стеке, массив в куче
  return x
}

Стек очищается автоматически при выходе из функции. Кучу очищает сборщик мусора (GC).

Hidden Classes (Maps)

JavaScript — динамический язык: свойства объекта можно добавлять и удалять на лету. Это должно быть медленно — каждый раз искать свойство по имени в хеш-таблице. Но V8 решает эту проблему через hidden classes (внутренне называются Maps).

Hidden class описывает форму (shape) объекта — какие свойства, в каком порядке, по каким смещениям:

const a = { x: 1, y: 2 }
const b = { x: 3, y: 4 }
// a и b разделяют один hidden class: { x: offset 0, y: offset 1 }

const c = { y: 1, x: 2 }
// c имеет ДРУГОЙ hidden class — порядок свойств отличается!

Когда V8 знает hidden class, доступ к свойству — это чтение по фиксированному смещению, как в C-структуре.

Shape Transitions

Добавление свойства создаёт transition — переход к новому hidden class:

const obj = {}     // HiddenClass_0: {}
obj.x = 1          // HiddenClass_1: { x: offset 0 }
obj.y = 2          // HiddenClass_2: { x: offset 0, y: offset 1 }

V8 строит дерево переходов. Если два объекта проходят одинаковую последовательность добавлений, они получают один hidden class:

function createPoint(x, y) {
  const p = {}
  p.x = x   // transition: HC_0 → HC_1
  p.y = y   // transition: HC_1 → HC_2
  return p
}

const p1 = createPoint(1, 2) // HC_2
const p2 = createPoint(3, 4) // HC_2 — тот же hidden class!

Но если порядок добавления отличается:

const a = {}; a.x = 1; a.y = 2 // HC_A
const b = {}; b.y = 1; b.x = 2 // HC_B — другой hidden class!

Правило: инициализируй свойства объектов в одинаковом порядке.

Inline Caches (IC)

Inline cache — механизм ускорения повторного доступа к свойствам. При первом доступе к obj.x V8 ищет свойство по hidden class. При повторном — использует кэшированный результат.

Три состояния IC:

СостояниеОписаниеСкорость
MonomorphicВсе объекты имеют один hidden classМаксимальная
Polymorphic2-4 разных hidden classСредняя
Megamorphic5+ hidden classМинимальная (хеш-таблица)
function getX(obj) { return obj.x }

// Мономорфный: все объекты одного shape
getX({ x: 1, y: 2 })
getX({ x: 3, y: 4 }) // быстро — IC hit

// Полиморфный: два разных shape
getX({ x: 1, y: 2 })
getX({ x: 1, z: 3 }) // медленнее — два варианта в IC

// Мегаморфный: много разных shapes
getX({ x: 1, a: 1 })
getX({ x: 1, b: 2 })
getX({ x: 1, c: 3 })
getX({ x: 1, d: 4 })
getX({ x: 1, e: 5 }) // IC переключился в megamorphic — медленный lookup

Правило: в горячих путях передавай объекты с одинаковой структурой.

Pointer Tagging и SMI Optimization

V8 использует pointer tagging — младший бит указателя кодирует тип:

  • 0 (чётный) — указатель на объект в куче (HeapObject)
  • 1 (нечётный) — SMI (Small Integer) — целое число, упакованное прямо в указатель
0x0000000A  →  указатель на объект (младший бит = 0)
0x0000000B  →  SMI со значением 5 (0xB >> 1 = 5, младший бит = 1)

SMI не требуют аллокации в куче — число хранится прямо в «указателе». Это экономит память и ускоряет арифметику для целых чисел в диапазоне от -2³⁰ до 2³⁰-1 (32-bit) или -2³¹ до 2³¹-1 (64-bit с pointer compression).

Числа за пределами SMI-диапазона и дробные числа хранятся как HeapNumber — отдельный объект в куче.

Итого

КонцепцияОписание
StackПримитивы, локальные переменные, адреса возврата — быстрая, автоматическая очистка
HeapОбъекты, массивы, замыкания — сборщик мусора
Hidden ClassesОписывают форму объекта, позволяют доступ к свойствам по смещению
Shape TransitionsДерево переходов при добавлении свойств
Inline CachesКэш результатов поиска свойств: monomorphic → polymorphic → megamorphic