Когда Garbage Collector начинает очищать объекты?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Когда 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)
- Переиспользуйте объекты
- Удаляйте ссылки, когда они не нужны