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

Когда Garbage Collector начинает очищать объекты?

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

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

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

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

Когда Garbage Collector начинает очищать объекты

Garbage Collector (GC) — это процесс, который автоматически удаляет неиспользуемые объекты из памяти. Понимание, когда и как работает GC, критично для оптимизации производительности Android приложений.

Главное правило: Когда нет ссылок на объект

Объект удаляется из памяти, когда больше нет ссылок на него:

fun example() {
    // Объект создан, есть ссылка person
    var person = Person("Alice", 30)
    
    // person удалится когда функция завершится
    // или переменная перезапишется
} // GC может удалить person здесь

fun example2() {
    var person: Person? = Person("Alice", 30)
    person = null  // Ссылка удалена, GC может очистить объект
    
    person = Person("Bob", 25)  // Предыдущий Person удален
}

Граф ссылок (Reference Graph)

GC отслеживает граф ссылок. Объект считается достижимым (reachable), если на него есть хоть одна ссылка из корней:

class App {
    val users = mutableListOf<User>()
    
    fun addUser(user: User) {
        users.add(user)  // User достижим через: App -> users -> User
    }
    
    fun removeUser(user: User) {
        users.remove(user)  // User становится недостижимым
    }
}

val app = App()  // корень
val user = User("Alice")
app.addUser(user)
app.removeUser(user)  // user теперь не достижим -> может быть очищен

Типы ссылок

1. Strong Reference (обычная ссылка)

val obj = MyClass()  // Strong reference
obj  // Объект живет, пока есть эта ссылка

Объект очищается только когда все strong ссылки удалены.

2. Weak Reference

import java.lang.ref.WeakReference

val obj = MyClass()
val weakRef = WeakReference(obj)

obj = null  // Strong ссылка удалена
// GC может удалить MyClass, даже если weakRef существует

weakRef.get()  // null

Используется для:

  • Кеширования (если память нужна, объект удалится)
  • Listener'ов (чтобы не было memory leak'а)
  • View holder'ов в Adapter'ах
class MyListener @Inject constructor(
    private val context: Context
) {
    private val weakContext = WeakReference(context)
    
    fun doSomething() {
        val ctx = weakContext.get() ?: return  // Context может быть удален
        // Используем ctx
    }
}

3. Soft Reference

import java.lang.ref.SoftReference

val obj = MyClass()
val softRef = SoftReference(obj)

Объект удаляется только когда память критически нужна. Идеален для кеширования.

4. Phantom Reference

Оченьредко используется, нужна для отслеживания удаления объекта.

Процесс сборки мусора

Шаг 1: Mark (Пометка достижимых объектов)

GC начинает с корней (static ссылки, локальные переменные на стеке) и идёт по всему графу:

Корни (Stack, static fields)
  |
  v
App object -----> users List -----> User("Alice")
                       |
                       v
                   User("Bob")

Все эти объекты ПОМЕЧЕНЫ (marked)

Шаг 2: Sweep (Удаление непомеченных объектов)

Всё, что не помечено, удаляется из памяти.

Когда именно вызывается GC

Eсть несколько триггеров:

1. Когда памяти недостаточно

// Создаём много объектов
for (i in 0..10000) {
    val obj = VeryLargeObject()  // 1MB каждый
    // Когда памяти << 50MB, вызывается GC
}

2. После определённого количества выделений памяти

Dalvik/ART запускают GC примерно каждые 1-2 МБ новых объектов (зависит от настроек).

3. Явный вызов (не рекомендуется)

System.gc()  // Просьба запустить GC, но это не гарантирует

4. При давлении памяти

На Android, если памяти мало, система запускает GC более агрессивно.

Memory Leak: Объект не удаляется, хотя должен

class Activity : AppCompatActivity() {
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
 n        
        val listener = MyListener()
        
        // Memory leak! Service держит ссылку на Activity
        // Когда Activity завершится, она не удалится
        MyService.registerListener(listener)
    }
}

// Правильно: использовать WeakReference
class MyService {
    companion object {
        private val listeners = mutableListOf<WeakReference<MyListener>>()
        
        fun registerListener(listener: MyListener) {
            listeners.add(WeakReference(listener))
        }
    }
}

Цикл жизни объекта

1. Создание (allocation)
   val obj = MyClass()

2. Использование (in use)
   obj.doSomething()

3. Последняя ссылка удалена (unreachable)
   obj = null

4. GC отмечает объект для удаления (mark)
   GC.run()

5. Память освобождена (sweep)
   Объект удалён из памяти

Типы GC на Android

Generational GC (молодое и старое поколение)

Young Generation (Eden, Survivor)
  - Объекты, созданные недавно
  - GC здесь частый и быстрый

Old Generation
  - Долгоживущие объекты
  - GC здесь редкий и медленный

Stop-the-world (STW)

Во время GC весь код приложения останавливается:

fun heavyOperation() {
    repeat(100) {
        // GC может произойти здесь, остановив весь код на 10-100ms
        val obj = HeavyObject()
    }
}

Это вызывает jank (заикание) в UI, если GC происходит на главном потоке.

Оптимизация для уменьшения GC

1. Переиспользуйте объекты (Object pooling)

// Плохо: создаём новые объекты в цикле
for (i in 0..1000) {
    val rect = Rect(0, 0, 100, 100)  // 1000 объектов
}

// Хорошо: переиспользуем один объект
val rect = Rect()
for (i in 0..1000) {
    rect.set(0, 0, 100, 100)
}

2. Избегайте временных объектов

// Плохо
fun process(items: List<Int>): List<Int> {
    return items.map { it * 2 }.filter { it > 10 }
    // Создаёт 2 промежуточных List'а
}

// Лучше
fun process(items: List<Int>): List<Int> {
    return items.asSequence()
        .map { it * 2 }
        .filter { it > 10 }
        .toList()  // Один промежуточный список
}

3. Используйте SparseArray вместо HashMap

// На Android SparseArray более эффективен
// Плохо
val map = HashMap<Int, String>()

// Лучше (на Android)
val sparseArray = SparseArray<String>()

Проверка GC в логах

adb logcat | grep GC

# Вывод:
# I/GC: Explicit concurrent GC, 2.5MB freed
# I/GC: Concurrent mark sweep GC freed 5.2MB

Ключевой вывод

GC удаляет объекты когда:

  • Нет ссылок на объект (strong references)
  • GC запущен (вызван, или автоматически)
  • Объект помечен как недостижимый (unreachable)

Это автоматический процесс, вы не контролируете его, но можете помочь GC:

  • Избегайте memory leak'ов (unused listeners, contexts)
  • Переиспользуйте объекты
  • Удаляйте ссылки, когда они не нужны