Вопрос 3 · Раздел 3

Что хранится в Stack?

4. Virtual Threads (Java 21+) для high-concurrency. Не используйте для CPU-bound задач — они не дают прироста и добавляют оверхед на монтирование/демонтирование. 5. Escape Analy...

Версии по языкам: English Russian Ukrainian

🟢 Junior Level

Stack (Стек) — приватная область памяти для каждого потока.

Зачем свой стек каждому потоку: у каждого потока своя цепочка вызовов методов. Потоки не могут共享 один Stack — иначе локальные переменные одного потока были бы видны другому.

Простая аналогия: Стопка тарелок. Когда вызываете метод — кладёте тарелку наверх. Когда метод завершается — убираете тарелку. Всегда работаете с верхней тарелкой.

Что хранится:

  • Примитивы: int x = 10;, double price = 99.9;
  • Ссылки на объекты: User user = ...; (ссылка в Stack, объект в Heap)
  • Информация о вызовах методов

Пример:

public void methodA() {
    int a = 5;         // Stack
    methodB();         // Новый фрейм на стеке
}

public void methodB() {
    int b = 10;        // Stack (в своём фрейме)
    // Когда метод завершится → b удалится
}

Размер Stack: -Xss (по умолчанию 1 МБ) Ошибка: StackOverflowError при бесконечной рекурсии


🟡 Middle Level

Структура стекового фрейма

Stack Frame (при вызове метода):
┌─────────────────────────────────┐
│ Local Variable Table            │
│   [0] = this                    │
│   [1] = param1                  │
│   [2] = param2                  │
│   [3] = local var               │
├─────────────────────────────────┤
│ Operand Stack                   │
│   Для вычислений: push, pop     │
│   a + b → push a, push b, add   │
├─────────────────────────────────┤
│ Dynamic Linking                 │
│   → Constant Pool (методы, поля)│
├─────────────────────────────────┤
│ Return Address                  │
│   → Куда вернуться              │
└─────────────────────────────────┘

Что именно в Stack

public class Example {
    public void process(int x) {
        int local = x * 2;        // Stack (примитив)
        String s = "hello";       // Stack (ссылка) → "hello" в Heap
        User user = new User();   // Stack (ссылка) → объект в Heap
        int[] arr = new int[10];  // Stack (ссылка) → массив в Heap
    }
}

ВАЖНО: В Stack хранятся только ссылки на объекты, не сами объекты!

StackWalker API (Java 9+)

// Устаревший способ (тяжёлый)
StackTraceElement[] stack = new Throwable().getStackTrace();

// Современный способ (ленивый, быстрый)
StackWalker walker = StackWalker.getInstance();
walker.forEach(frame -> System.out.println(frame.getClassName()));

// С фильтрацией
String caller = StackWalker.getInstance()
    .walk(frames -> frames
        .skip(1)  // Пропустить текущий метод
        .findFirst()
        .map(StackWalker.StackFrame::getClassName)
        .orElse(null));

Типичные ошибки

  1. Глубокая рекурсия
    // ❌ StackOverflowError
    public int factorial(int n) {
        return n * factorial(n - 1);  // Нет базового случая!
    }
    

🔴 Senior Level

Stack Frame: Under the Hood

JVM Specification:
  Фрейм создаётся при вызове метода
  Размер вычисляется при компиляции (известно кол-во локальных переменных)

Local Variable Table:
  - Массив слов (32 бита каждое)
  - long/double занимают 2 слота
  - Индекс 0 = 'this' для нестатических методов
  - Параметры методов идут первыми

Operand Stack:
  - Глубина тоже известна при компиляции
  - Используется для byte-code инструкций
  - Пример: a + b + c
    → push a → push b → iadd → push c → iadd

Virtual Threads и Stack (Java 21+)

Платформенные потоки (до Java 21):
  Stack = фиксированный кусок нативной памяти (1 МБ через -Xss)
  → Ограничение: ~1000 потоков на 1 ГБ RAM
  → Нельзя изменить размер на лету

Виртуальные потоки (Java 21+):
  Stack = объект в Heap!
  → Mount: копируется в Carrier Thread stack
  → Yield (блокировка): копируется обратно в Heap
  → Результат: миллионы потоков!
  
Память на виртуальный поток:
  → ~2 КБ — начальное значение для пустого виртуального потока. Стек растёт динамически при углублении вызовов.
  → Динамически растёт при необходимости

Tail Call Optimization (TCO)

// Хвостовая рекурсия
public int sum(int n, int acc) {
    if (n == 0) return acc;
    return sum(n - 1, acc + n);  // Хвостовой вызов
}

// В языках с TCO → переиспользуется тот же фрейм
// Java НЕ поддерживает T напрямую!
// Но JIT делает Method Inlining:

// После Inlining:
public int sum(int n, int acc) {
    while (n != 0) {
        acc += n;
        n--;
    }
    return acc;
}
// → 1 фрейм вместо N!

Stack и Escape Analysis

// JIT может аллоцировать объект на Stack!
public void process() {
    Point p = new Point(10, 20);  // Обычно в Heap
    
    // Если JIT видит, что 'p' не "убежит" из метода:
    // 1. Scalar Replacement: развалить на int x=10, y=20
    // 2. Stack Allocation: создать на Stack
    // 3. Lock Elision: убрать synchronized если один поток
    
    System.out.println(p.x + p.y);  // Используется только тут
}

// Проверка: -XX:+PrintEscapeAnalysis -XX:+DoEscapeAnalysis

Stream API как Iterator

// Stream = продвинутый Iterator с lazy evaluation
list.stream()
    .filter(s -> s.startsWith("A"))   // Не выполняется пока нет terminal
    .map(String::toUpperCase)         // Не выполняется
    .forEach(System.out::println);    // Terminal → запускает pipeline

// В отличие от Iterator:
// 1. Composability — можно строить цепочки
// 2. Lazy — вычисления только когда нужны
// 3. Parallel — автоматический параллелизм
// 4. Functional — без побочных эффектов

Production Experience

Реальный сценарий: StackOverflowError в production

  • Deep nested JSON parsing → рекурсия 10,000+ уровней
  • -Xss = 1 МБ → StackOverflowError
  • Решение:
    1. Увеличить -Xss до 2 МБ (временное)
    2. Переписать парсер на итеративный (постоянное)

Реальный сценарий: Virtual Threads спасли сервер

  • REST API: 10,000 concurrent запросов
  • Platform threads: 10,000 × 1 МБ = 10 ГБ RAM на стеки
  • Virtual threads: 10,000 × ~2 КБ = 20 МБ в Heap
  • Экономия: 99.8% RAM!

Best Practices

  1. Stream API для сложных трансформаций
  2. StackWalker для анализа цепочки вызовов
  3. Избегайте глубокой рекурсии — используйте итерацию
  4. Virtual Threads (Java 21+) для high-concurrency. Не используйте для CPU-bound задач — они не дают прироста и добавляют оверхед на монтирование/демонтирование.
  5. Escape Analysis помогает JIT оптимизировать аллокации

Резюме для Senior

  • Stack = fragmented execution memory, frame-per-frame
  • Virtual Threads = стек в Heap → dynamic sizing → millions of threads. Не для CPU-bound задач.
  • StackWalker = lazy stack inspection, +performance
  • Escape Analysis = Stack Allocation + Lock Elision
  • TCO не поддерживается, но Method Inlining решает
  • Stream API = lazy Iterator с composability

🎯 Шпаргалка для интервью

Обязательно знать:

  • Stack хранит: локальные примитивы, ссылки на объекты, информацию о вызовах методов
  • Каждый поток имеет свой Stack (по умолчанию 1 МБ, параметр -Xss)
  • Стековый фрейм: Local Variable Table + Operand Stack + Dynamic Linking + Return Address
  • При выходе из метода фрейм удаляется мгновенно — GC не нужен
  • Virtual Threads (Java 21+): стек в Heap как объект, ~2 КБ вместо 1 МБ
  • JIT может аллоцировать объект на Stack через Escape Analysis, если он не «убегает»
  • Java не поддерживает Tail Call Optimization напрямую, но JIT делает Method Inlining

Частые уточняющие вопросы:

  • Что будет при бесконечной рекурсии?StackOverflowError, Stack переполнен
  • Почему Virtual Threads позволяют миллионы потоков? — Стек в Heap, динамический размер (~2 КБ vs 1 МБ фиксировано)
  • Можно ли изменить размер Stack на лету? — Нет, -Xss задаётся при старте JVM. Virtual Threads решают это динамическим размером.
  • Что такое Operand Stack? — Стек операндов внутри фрейма для byte-code инструкций (push/pop/add)

Красные флаги (НЕ говорить):

  • «Объекты хранятся в Stack» — в Stack хранятся только ссылки на объекты
  • «Stack общий для всех потоков» — Stack приватный для каждого потока
  • «Java поддерживает TCO» — Java НЕ поддерживает Tail Call Optimization

Связанные темы:

  • [[1. В чём разница между Heap и Stack]]
  • [[2. Что хранится в Heap]]
  • [[4. Что такое Garbage Collection]]
  • [[16. Что такое stop-the-world]]
  • [[18. Что такое параметры -Xms и -Xmx]]