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

Что такое память на стеке?

2.0 Middle🔥 201 комментариев
#JVM и память#Производительность и оптимизация

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

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

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

Память на стеке (Stack Memory)

Память на стеке (Stack) — это автоматически управляемая область памяти, которая работает по принципу LIFO (Last In First Out). В стеке хранятся примитивные значения и ссылки на объекты, а также информация о вызванных методах.

Как работает стек

Когда вызывается метод, для него создаётся stack frame (кадр стека), который содержит:

  • Локальные переменные
  • Параметры метода
  • Адрес возврата

Когда метод заканчивается, его frame удаляется из стека:

fun main() {
    val a = 10          // frame main: [a=10]
    methodOne(a)        // frame methodOne добавлена в стек
    println(a)          // frame methodOne удалена, остаётся main
}

fun methodOne(x: Int) {
    val b = x + 5       // frame methodOne: [x=10, b=15]
    methodTwo(b)        // frame methodTwo добавлена
}                       // frame methodOne удалена

fun methodTwo(y: Int) {
    val c = y * 2       // frame methodTwo: [y=15, c=30]
}                       // frame methodTwo удалена

Что хранится в стеке

1. Примитивные типы (полностью):

val intValue: Int = 42          // Весь int в стеке
val floatValue: Float = 3.14f   // Весь float в стеке
val boolValue: Boolean = true   // Весь boolean в стеке
val charValue: Char = 'A'       // Весь char в стеке

2. Ссылки на объекты (только ссылка):

val user: User = User("John")   // Ссылка на объект в стеке
                                 // Сам объект User в HEAP

val numbers: List<Int> = listOf(1, 2, 3)  // Ссылка на List в стеке
                                           // Сам List объект в HEAP

3. Локальные переменные методов:

fun calculateSum(a: Int, b: Int): Int {
    val sum = a + b              // Локальная переменная sum в стеке
    val multiplied = sum * 2     // Локальная переменная multiplied в стеке
    return multiplied
}
// Когда метод завершится, sum и multiplied удаляются из стека

Стек vs Heap

АспектСтек (Stack)Куча (Heap)
Управление памятьюАвтоматическое (LIFO)Управление сборщиком мусора
Что хранитсяПримитивы, ссылки, локальные переменныеОбъекты, массивы, строки
СкоростьОчень быстроМедленнее чем стек
РазмерОграничен (обычно МБ)Больше, чем стек
МногопоточностьКаждый поток имеет свой стекОбщая куча для всех потоков
Ошибка переполненияStackOverflowErrorOutOfMemoryError

Пример с диаграммой памяти

class User(val name: String, val age: Int)

fun main() {
    val user1 = User("Alice", 25)   // СТЕК: user1 -> адрес объекта
                                      // HEAP: User("Alice", 25)
    
    val user2 = user1                // СТЕК: user2 -> тот же адрес
                                      // HEAP: User("Alice", 25) (один объект!)
    
    val numbers = listOf(1, 2, 3)    // СТЕК: numbers -> адрес List
                                      // HEAP: List([1,2,3])
}

// После завершения main:
// - СТЕК: очищается (user1, user2, numbers удаляются)
// - HEAP: объекты помечаются для garbage collection

StackOverflowError

Ошибка возникает при переполнении стека, обычно из-за бесконечной рекурсии:

// ❌ StackOverflowError
fun recursiveWithoutBase(n: Int): Int {
    return recursiveWithoutBase(n + 1)  // Нет условия выхода!
}

// Каждый вызов добавляет frame в стек
// Стек переполняется -> StackOverflowError

// ✅ Правильная рекурсия с базовым случаем
fun factorial(n: Int): Long {
    if (n <= 1) return 1  // Базовый случай - выход из рекурсии
    return n * factorial(n - 1)
}

Жизненный цикл переменных в стеке

fun example() {
    val x = 10      // x добавлена в стек
    
    if (true) {
        val y = 20  // y добавлена в стек frame if
    }               // y удалена из стека (выход из области видимости)
    
    // println(y)  // Ошибка компиляции: y не существует
    println(x)      // x ещё в стеке
}                   // x удалена из стека, функция завершена

Многопоточность и стек

Каждый поток имеет свой стек, что позволяет безопасно использовать локальные переменные в многопоточном коде:

fun main() {
    // Поток 1 имеет свой стек
    thread {
        val threadLocal = 10    // Локально для потока 1
        Thread.sleep(1000)
        println(threadLocal)    // Безопасно, не конфликтует с потоком 2
    }
    
    // Поток 2 имеет свой стек
    thread {
        val threadLocal = 20    // Локально для потока 2
        println(threadLocal)    // Независимо от потока 1
    }
}

Best Practices

  1. Избегайте глубокую рекурсию — может привести к StackOverflowError
  2. Используйте итерацию вместо рекурсии для больших данных:
// ❌ Рекурсия для больших n
fun sum(n: Int): Long {
    if (n <= 0) return 0
    return n + sum(n - 1)  // Может переполнить стек
}

// ✅ Итерация безопаснее
fun sum(n: Int): Long {
    var total = 0L
    for (i in 1..n) total += i
    return total
}
  1. Помните о размере стека — для потоков часто задают меньший размер
  2. Не сохраняйте большие объекты в стеке — они должны быть в heap

Понимание стека критично для оптимизации производительности и избежания ошибок памяти в Android приложениях.

Что такое память на стеке? | PrepBro