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

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

2.0 Middle🔥 171 комментариев
#JVM и управление памятью

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

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

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

# Где хранятся ссылочные данные в памяти JVM?

Это классический вопрос о структуре памяти JVM, который проверяет глубину понимания того, как работает Java под капотом. Нужно различать примитивные и ссылочные типы.

Короткий ответ

Ссылочные данные (объекты) хранятся в Heap (куче), а сами ссылки хранятся в Stack (стеке).

Stack                 Heap
┌─────────────────┐   ┌──────────────────────┐
│                 │   │                      │
│ user ──────────────→ | User object          │
│  ↓              │   │ {name: "John",       │
│  (ссылка)       │   │  age: 30}            │
│                 │   │                      │
└─────────────────┘   └──────────────────────┘

Детальное объяснение

Примитивные типы

int age = 25;
boolean isActive = true;
double salary = 5000.0;

// Хранятся в Stack:
Stack:
┌─────────────────┐
│ age = 25        │
│ isActive = true │
│ salary = 5000.0 │
└─────────────────┘

Ссылочные типы

String name = "John";
User user = new User("Jane", 30);
List<Integer> numbers = new ArrayList<>();

// Ссылки хранятся в Stack, объекты в Heap:
Stack                         Heap
┌──────────────────┐         ┌─────────────────────┐
│ name ────────────────→      │ "John" (String)     │
│                  │         │                     │
│ user ────────────────→      │ User {name: "Jane", │
│                  │         │       age: 30}      │
│                  │         │                     │
│ numbers ─────────────→      │ ArrayList []        │
│                  │         └─────────────────────┘
└──────────────────┘

Разные области памяти JVM

1. Stack (Стек)

Характеристики:

  • Хранит ссылки и примитивные переменные
  • Организован как LIFO (Last In, First Out)
  • Один stack на каждый поток (thread-safe)
  • Память автоматически освобождается при выходе из области видимости
  • Размер ограничен (обычно -Xss1024k)
  • Быстрая работа
public void example() {
    int x = 10;          // Stack: x = 10
    String s = "hello";  // Stack: s → (ссылка), Heap: "hello"
    
    {  // Вложенный блок
        int y = 20;      // Stack: y = 20
    }  // y удалена из stack
    
    // x все еще в stack
}  // x и s удалены при выходе из метода

2. Heap (Куча)

Характеристики:

  • Хранит все объекты (String, User, ArrayList и т.д.)
  • Общая для всех потоков
  • Управляется Garbage Collector
  • Размер ограничен (обычно -Xmx1024m)
  • Медленнее чем Stack
  • Может вызвать OutOfMemoryError
User user1 = new User("John");     // Heap: User object
User user2 = new User("Jane");     // Heap: другой User object
User user3 = user1;                // Stack: user3 → тот же объект что user1

// Heap содержит 2 объекта User
// Stack содержит 3 ссылки (user1, user2, user3)

3. String Pool (специальная область в Heap)

// String Literal Pool в Heap
String s1 = "hello";  // Создается один раз в pool
String s2 = "hello";  // Ссылается на тот же объект в pool
String s3 = new String("hello");  // Новый объект в обычной heap

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

4. PermGen / Metaspace (Java 8+)

До Java 8: PermGen

Хранит:
- Метаданные класса
- Константы
- Code кэш

Java 8+: Metaspace

Хранит:
- Метаданные класса
- Методы
- Информация о типах
public class MyClass {
    public static final String CONSTANT = "value";
    // Метаданные MyClass, CONSTANT → Metaspace
    // String value → Heap
}

Визуальная схема памяти JVM

┌─────────────────────────────────────────────────────────┐
│                   JVM Memory                            │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  ┌──────────────────────────────────────────────────┐  │
│  │                  Stack (Thread 1)                │  │
│  ├──────────────────────────────────────────────────┤  │
│  │ local variables, ссылки на объекты              │  │
│  │ size: ограничено (-Xss)                         │  │
│  └──────────────────────────────────────────────────┘  │
│                                                         │
│  ┌──────────────────────────────────────────────────┐  │
│  │                  Stack (Thread 2)                │  │
│  ├──────────────────────────────────────────────────┤  │
│  │ (другой stack для другого потока)               │  │
│  └──────────────────────────────────────────────────┘  │
│                                                         │
│  ┌──────────────────────────────────────────────────┐  │
│  │                    Heap                          │  │
│  ├──────────────────────────────────────────────────┤  │
│  │ Young Generation (Eden, S0, S1)                 │  │
│  │ Old Generation                                   │  │
│  │ Размер: ограничено (-Xmx)                       │  │
│  │ GC управляет этой областью                      │  │
│  └──────────────────────────────────────────────────┘  │
│                                                         │
│  ┌──────────────────────────────────────────────────┐  │
│  │                 Metaspace (Java 8+)             │  │
│  ├──────────────────────────────────────────────────┤  │
│  │ Metadata классов, методы, информация о типах    │  │
│  │ Может использовать Native Memory                │  │
│  └──────────────────────────────────────────────────┘  │
│                                                         │
│  ┌──────────────────────────────────────────────────┐  │
│  │              Code Cache                         │  │
│  ├──────────────────────────────────────────────────┤  │
│  │ Скомпилированный код (JIT компилятор)          │  │
│  └──────────────────────────────────────────────────┘  │
│                                                         │
└─────────────────────────────────────────────────────────┘

Практический пример

public class MemoryExample {
    public static void main(String[] args) {
        // 1. Stack
        int x = 10;                    // Stack: x = 10 (примитив)
        
        // 2. Heap + Stack
        String name = "Alice";         // Stack: name (ссылка)
                                       // Heap: "Alice" (String)
        
        // 3. Heap + Stack
        User user = new User("Bob", 25);  // Stack: user (ссылка)
                                          // Heap: User объект
        
        // 4. Heap + Stack
        List<Integer> numbers = new ArrayList<>();
        numbers.add(1);                // Stack: numbers (ссылка)
                                       // Heap: ArrayList объект
        
        processData(x, user, numbers);
    }
    
    public static void processData(int num, User user, List<Integer> list) {
        // Stack (новый frame для этого метода):
        // - num = 10
        // - user → (ссылка на объект в Heap)
        // - list → (ссылка на ArrayList в Heap)
    } // Stack frame удален при выходе из метода
}

// Где каждая переменная:
// x → Stack
// name → Stack (ссылка), "Alice" → String Pool (часть Heap)
// user → Stack (ссылка), User{} → Heap
// numbers → Stack (ссылка), ArrayList → Heap

OutOfMemoryError: два типа

1. java.lang.OutOfMemoryError: Java heap space

// ❌ Heap переполнен объектами
List<byte[]> list = new ArrayList<>();
while (true) {
    list.add(new byte[1024 * 1024]);  // Добавляем 1MB
}
// Ошибка: OutOfMemoryError: Java heap space

Решение: увеличить heap

java -Xmx1024m MyApp

2. java.lang.OutOfMemoryError: PermGen space (Java 7)

// ❌ PermGen переполнен метаданными класса
while (true) {
    URL classUrl = new URL("file://ClassFile.class");
    URLClassLoader loader = new URLClassLoader(new URL[]{classUrl});
    loader.loadClass("ClassFile");
    // Много классов загружено, PermGen переполнен
}

Решение (Java 7):

java -XX:PermSize=128m -XX:MaxPermSize=256m MyApp

Решение (Java 8+): Metaspace использует native memory, обычно проблем нет

StackOverflowError

// ❌ Stack переполнен рекурсией
public static void infiniteRecursion() {
    infiniteRecursion();  // Бесконечная рекурсия
}

// Ошибка: java.lang.StackOverflowError
// Каждый вызов добавляет frame в stack

// ✅ Правильно
public static int factorial(int n) {
    if (n <= 1) return 1;  // Base case
    return n * factorial(n - 1);
}

Best Practices

1. ✅ Примитивные типы → Stack
2. ✅ Ссылки → Stack, объекты → Heap
3. ✅ Помни о String Pool при работе со строками
4. ✅ Явно обнуляй большие ссылки если больше не нужны
5. ✅ Избегай утечек памяти (unclosed resources)
6. ✅ Используй try-with-resources для автоматического закрытия
7. ❌ НЕ создавай бесконечные рекурсии (StackOverflowError)
8. ❌ НЕ храни огромные коллекции без надобности

// Пример: try-with-resources (автоматически закрывает ресурс)
try (FileInputStream fis = new FileInputStream("file.txt")) {
    // Работа с файлом
} // Файл автоматически закрыт

// Против: ручное управление
FileInputStream fis = new FileInputStream("file.txt");
try {
    // Работа
} finally {
    fis.close();  // Не забыть закрыть!
}

Команды мониторинга памяти

# Просмотр параметров JVM
jps -l                          # Список Java процессов

# Детальная информация о памяти
jstat -gc <pid> 1000           # Вывод GC статистики каждую секунду

# Heap dump
jmap -dump:live,format=b,file=heap.bin <pid>

# Анализ heap dump
jhat heap.bin                   # Анализ дампа памяти