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

Где хранится State при передаче между suspend функциями?

2.3 Middle🔥 111 комментариев
#Kotlin основы

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

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

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

Где хранится State при передаче между suspend функциями

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

State хранится в стеке вызовов (call stack) каждой корутины. Когда suspend функция приостанавливается, её локальные переменные и параметры сохраняются, а когда возобновляется — восстанавливаются из той же памяти.

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

1. Локальные переменные в suspend функции

suspend fun getUserData(userId: String): User {
  val userResponse = fetchUserFromApi(userId)  // Приостановится здесь
  val posts = fetchUserPosts(userId)  // Потом здесь
  return User(userResponse, posts)
}

Когда fetchUserFromApi() приостанавливает корутину:

  • userResponse еще не инициализирована
  • posts еще не инициализирована
  • userId хранится в continuation object

Когда корутина возобновляется:

  • userResponse восстанавливается из памяти
  • Выполнение продолжается со строки, где произошла приостановка

2. Continuation Object — ключ к пониманию

Continuation — это объект, который хранит весь state suspend функции:

// Внутренне Kotlin преобразует suspend функцию в:
fun getUserData(
  userId: String,
  continuation: Continuation<User>
): Any {
  // State machine для управления приостановками
  when (continuation.state) {
    0 -> {
      // Первое вызова - инициализация
      return fetchUserFromApi(userId, object : Continuation<UserResponse> {
        override fun resumeWith(result: Result<UserResponse>) {
          continuation.state = 1  // Переходим в следующее состояние
        }
      })
    }
    1 -> {
      // Вернулись из первой приостановки
      val userResponse = continuation.userResponse  // Восстановили
      return fetchUserPosts(userId, object : Continuation<List<Post>> {
        // ...
      })
    }
  }
}

Память: Stack vs Heap

На стеке (Stack)

Локальные переменные suspend функции первоначально хранятся на стеке:

suspend fun example() {
  val x = 10  // На стеке
  val name = "John"  // На стеке
  delay(1000)  // Приостановка
}

Когда корутина приостанавливается, эти переменные перемещаются в Continuation object.

На куче (Heap)

Continuation object — это обычный Java объект, который живет на heap:

// Контекст хранит:
// - Состояние (какой шаг выполняется)
// - Локальные переменные
// - Ссылки на объекты
// - Ссылку на dispatcher

Практический пример: отслеживание state

suspend fun complexOperation(): String {
  println("1. Начало")  // State = 0
  
  val result1 = step1()  // Может приостановиться
  println("2. После step1, результат: $result1")  // State = 1
  
  val result2 = step2(result1)  // Может приостановиться
  println("3. После step2, результат: $result2")  // State = 2
  
  return "$result1 + $result2"  // State = 3
}

suspend fun step1(): String {
  delay(100)  // Приостановка
  return "A"
}

suspend fun step2(input: String): String {
  delay(100)  // Приостановка
  return "$input-B"
}

// Вызов:
viewModelScope.launch {
  val result = complexOperation()
  println("Итог: $result")  // "A + A-B"
}

Что происходит:

  1. Создается Continuation object для complexOperation
  2. Выполняется до первой приостановки (step1)
  3. State = 1, result1 = "A" сохраняется в Continuation
  4. Корутина приостанавливается
  5. Когда step1 завершается, корутина возобновляется
  6. result1 восстанавливается из Continuation
  7. Выполняется до следующей приостановки
  8. И так далее...

Несколько корутин — несколько Continuation objects

launchIO {
  // Continuation #1
  val user1 = fetchUser(1)  // Может приостановиться
  println(user1)
}

launchIO {
  // Continuation #2 (отдельный объект)
  val user2 = fetchUser(2)  // Может приостановиться
  println(user2)
}

// Каждая корутина имеет свой Continuation с собственным state!

Где именно хранится?

1. Локальные переменные

  • Во время выполнения → стек (Stack)
  • Во время приостановки → Continuation object (Heap)

2. Параметры функции

  • Всегда → Continuation object (Heap)

3. Объекты

  • Всегда → Heap

4. Примитивы (Int, Boolean)

  • Во время выполнения → Стек
  • Во время приостановки → Continuation object (Heap)

Пример: отслеживание памяти

suspend fun trackMemory() {
  val primitive = 42  // Примитив
  val string = "Hello"  // Объект на heap
  
  println("До delay:")
  println("primitive в стеке, string на heap")
  
  delay(1000)  // ПРИОСТАНОВКА
  
  println("После delay:")
  println("primitive восстановлен из Continuation")
  println("string все еще на heap")
  
  // Обе переменные доступны!
  println("$primitive: $string")
}

Оптимизация: виртуальные потоки (Virtual Threads)

В Java 19+ есть virtual threads, которые работают по-другому:

// Virtual Thread в coroutine context
launchIO {
  // Может быть выполнена на разных virtual threads
  val data = fetchData()  // Может перейти на другой virtual thread
  processData(data)  // State хранится на virtual thread stack
}

Практическое значение

Зачем это знать?

  1. Memory leaks — если Continuation живет слишком долго
// ❌ УТЕЧКА: globalContinuation живет вечно
var globalContinuation: Continuation<*>? = null

suspend fun badExample() {
  globalContinuation = currentCoroutineContext()[ContinuationInterceptor]
  delay(1000)
}
  1. Производительность — слишком много приостановок = слишком много Continuation объектов

  2. Отладка — понимание stack trace'ов с suspend функциями

Итог

State при передаче между suspend функциями:

  • Хранится в Continuation object на Heap
  • Каждая корутина имеет свой Continuation
  • Локальные переменные "упаковываются" в Continuation при приостановке
  • При возобновлении переменные "распаковываются"
  • Это позволяет корутинам работать эффективнее обычных потоков (нет переключения контекста OS, меньше памяти)