← Назад к вопросам

Где хранятся данные при выполнении запроса?

2.0 Middle🔥 161 комментариев
#Базы данных и SQL

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Где хранятся данные при выполнении запроса в Java

В Java данные во время выполнения запроса распределяются между несколькими местами памяти. Понимание этого критично для оптимизации и отладки.

JVM Memory Layout

Общая структура памяти JVM:

┌─────────────────────────────────────────┐
│         HEAP (Кучa)                     │
│  Совместно для всех потоков             │
│  - Объекты                              │
│  - Arrays                               │
│  - Strings                              │
└─────────────────────────────────────────┘

┌─────────────────────────────────────────┐
│   STACK (Стек) × N потоков              │
│  Отдельный для каждого потока           │
│  - Примитивные типы                     │
│  - Ссылки на объекты                    │
│  - Локальные переменные                 │
└─────────────────────────────────────────┘

┌─────────────────────────────────────────┐
│      CODE (Код программы)               │
│  - Bytecode методов                     │
│  - Константы                            │
└─────────────────────────────────────────┘

┌─────────────────────────────────────────┐
│   METHOD AREA / METASPACE                │
│  - Определения классов                  │
│  - Код методов                          │
│  - Константы                            │
└─────────────────────────────────────────┘

Пример: Где хранятся данные при вызове метода

public class RequestHandler {
    // ← Это определение класса хранится в METASPACE
    
    public void handleRequest(String requestData) {
        // requestData (ссылка на String) → STACK
        // String объект сам → HEAP
        
        int userId = 123;  // примитив → STACK
        User user = userService.findById(userId);
        // user (ссылка) → STACK
        // User объект → HEAP
        
        List<Order> orders = new ArrayList<>();
        // orders (ссылка) → STACK
        // ArrayList объект → HEAP
        // Order объекты → HEAP
    }
    // По выходу: все данные со STACK удаляются
    // Объекты на HEAP остаются (пока есть ссылки)
}

1. STACK (Stack Memory)

Это LIFO (Last In First Out) структура, отдельная для каждого потока:

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

  • Примитивные типы (int, boolean, double)
  • Ссылки на объекты (не сами объекты!)
  • Информация о вызовах методов (stack frames)
  • Локальные переменные
public void example() {
    int x = 10;              // ← STACK: x = 10
    double y = 3.14;         // ← STACK: y = 3.14
    String name = "John";    // ← STACK: ссылка на String
    User user = new User();  // ← STACK: ссылка на User
    
    method2();  // ← STACK FRAME для method2 добавляется сверху
}               // ← По выходу все переменные удаляются (pop)

Преимущества Stack:

  • Очень быстрое выделение (просто сдвиг указателя)
  • Очень быстрое удаление (просто сдвиг указателя)
  • NO GC нужен
  • Поток-безопасно (каждому потоку свой stack)

Ограничения Stack:

  • Размер ограничен (обычно 1-8 MB на поток)
  • Данные удаляются при выходе из метода
  • StackOverflowError при рекурсии
public int factorial(int n) {
    if (n <= 1) return 1;
    return n * factorial(n - 1);  // ← Каждый вызов добавляет frame на STACK
}

// factorial(100000) → StackOverflowError!
// Слишком много frame'ов на stack'е

2. HEAP (Куча)

Общая память для всех потоков, управляется сборщиком мусора:

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

  • Все объекты (new Object())
  • Все массивы
  • Все String'и (если не в string pool)
  • Collections
  • Данные, которые нужны долго
public void allocateData() {
    // Все эти объекты → HEAP
    User user = new User("John");
    List<String> names = new ArrayList<>();
    names.add("Alice");
    names.add("Bob");
    
    Map<Integer, String> map = new HashMap<>();
    map.put(1, "value");
}
// Объекты остаются на HEAP, ссылки удаляются со STACK
// GC позже очистит неиспользуемые объекты

Преимущества Heap:

  • Неограниченный размер (почти)
  • Данные сохраняются пока есть ссылки
  • Глобально доступно для всех потоков

Ограничения Heap:

  • Медленнее Stack
  • Требует GC для очистки
  • Race conditions возможны

Типичная картина памяти при HTTP запросе

@RestController
public class UserController {
    
    @GetMapping("/users/{id}")
    public User getUser(@PathVariable Long id) {
        // STACK (поток обработки запроса):
        // - id (Long ссылка)
        // - request параметры
        // - localsthis reference
        
        User user = userService.findById(id);
        // ↑ STACK: user (ссылка)
        // ↑ HEAP: User объект + все поля
        
        List<Order> orders = orderService.findByUser(user);
        // ↑ STACK: orders (ссылка)
        // ↑ HEAP: ArrayList + Order объекты
        
        // При сериализации в JSON:
        // Jackson создаёт байт-массив на HEAP
        
        return user;  // JSON ответ
        // По завершении метода:
        // - STACK frame удаляется
        // - На HEAP остаются объекты (могут быть кэшированы)
        // - GC удалит если нет ссылок
    }
}

String Pool - специальный случай

String s1 = "hello";        // HEAP в string pool
String s2 = "hello";        // s2 указывает на тот же объект
String s3 = new String("hello");  // новый объект на HEAP

s1 == s2   // true, одна ссылка
s1 == s3   // false, разные объекты
s1.equals(s3)  // true, одинаковое содержимое

// Оптимизация:
String s4 = s3.intern();  // s4 теперь указывает на string pool версию

Метаспейс / PermGen - определения классов

// Когда ты загружаешь класс:
public class User { ... }
// Его определение хранится в METASPACE (Java 8+)
// или PermGen (Java 7 и ниже)

// METASPACE содержит:
// - Bytecode методов
// - Поля класса (тип и имя)
// - Константы
// - Статические переменные

// Статические переменные:
public static class Counter {
    static int count = 0;  // ← METASPACE/PermGen
}

Memory Regions во время обработки запроса

Запрос пришёл:
├─ HTTP Request объект → HEAP
├─ RequestServlet → METASPACE
├─ Handler методы → METASPACE (bytecode)
└─ Поток обработки:
   ├─ STACK (размер 1MB):
   │  ├─ request ссылка
   │  ├─ params
   │  └─ локальные переменные
   └─ HEAP:
      ├─ User объекты
      ├─ Collections
      ├─ JSON response buffer
      └─ ...

Ответ отправлен:
├─ STACK очищается
└─ HEAP объекты живут дальше (GC удалит позже)

Практический пример: анализ памяти

public class MemoryExample {
    static List<String> globalList = new ArrayList<>();  // METASPACE (ссылка)
    
    public static void process(String data) {
        // Локальные переменные → STACK текущего потока
        int count = 0;                      // STACK
        String message = "Processing";     // STACK (ссылка)
        
        // Объекты → HEAP
        List<String> localList = new ArrayList<>();  // STACK (ссылка), объект → HEAP
        localList.add(data);                         // data добавлен на HEAP
        
        globalList.add(data);  // также на HEAP, но ссылка в METASPACE
    }
    // STACK очищается: count, message, localList (ссылка)
    // HEAP: объекты остаются (data есть в globalList!)
}

OutOfMemoryError - какой тип

// StackOverflowError - заполнен STACK
recursion(recursion(recursion(...)))  // слишком глубоко

// OutOfMemoryError: Java heap space - заполнен HEAP
List<byte[]> list = new ArrayList<>();
while (true) {
    list.add(new byte[1024 * 1024]);  // 1MB каждый раз
}

// OutOfMemoryError: Metaspace - слишком много классов
// Обычно при динамической генерации классов
for (int i = 0; i < 1000000; i++) {
    loadDynamicClass("Class_" + i);
}

Оптимизация: куда положить данные

// ✅ Примитивы на STACK если возможно
public void good() {
    int x = 10;      // STACK
    double y = 3.14; // STACK
}

// ❌ Оборачивание примитивов в объекты
public void bad() {
    Integer x = 10;     // HEAP (autoboxing)
    Double y = 3.14;    // HEAP (autoboxing)
}

// ✅ Переиспользование объектов
StringBuilder sb = new StringBuilder();  // создать один раз
for (int i = 0; i < 1000; i++) {
    sb.append(i).append(",");  // переиспользовать
}

// ❌ Создание новых объектов в цикле
for (int i = 0; i < 1000; i++) {
    String result = "value" + i;  // новый String каждый раз → HEAP
}

Резюме

STACK:

  • Примитивные типы и ссылки
  • Локальные переменные метода
  • Очень быстро, no GC
  • Ограничен размером
  • Очищается при выходе из метода

HEAP:

  • Все объекты и массивы
  • Глобально доступно
  • Управляется GC
  • Медленнее, но больше
  • Существует пока есть ссылки

METASPACE:

  • Определения классов
  • Статические переменные
  • Bytecode методов
  • Не очищается GC (только при выгрузке класса)

Понимание этого — ключ к написанию эффективного Java кода и отладке проблем с памятью.

Где хранятся данные при выполнении запроса? | PrepBro