Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Garbage Collector Roots (GC Roots)
Garbage Collector Roots — это специальные объекты, которые не могут быть удалены, так как на них всегда есть ссылки из части JVM, управляемой операционной системой. GC использует roots как начальные точки для определения, какие объекты в памяти всё ещё используются.
Как работает Garbage Collection
Сборщик мусора использует алгоритм mark-and-sweep:
1. Mark (Пометить): GC начинает с roots и помечает все объекты, которые доступны из них
2. Sweep (Очистить): GC удаляет все непомеченные объекты
// Пример
var user: User? = User("John") // GC Root: локальная переменная
user = null // Объект больше не доступен из roots
// При next GC он будет удалён
Типы GC Roots
1. Локальные переменные в активных потоках (Local Variables)
fun main() {
val user = User("John") // GC Root: локальная переменная
val items = listOf(1, 2, 3) // GC Root: локальная переменная
// Оба объекта доступны для GC и остаются в памяти
process(user, items)
} // После выхода из метода, локальные переменные больше не roots
2. Параметры активных методов (Method Parameters)
fun processUser(user: User) { // user — GC Root
println(user.name)
} // После выхода — уже не root
fun processMany(users: List<User>) { // users — GC Root
users.forEach { println(it.name) }
} // После выхода — уже не root
3. Статические поля класса (Static Fields)
object UserRepository {
val cache = mutableMapOf<Int, User>() // GC Root: статическое поле
// Живёт столько же, сколько загружен класс
}
class Manager {
companion object {
val instance = Manager() // GC Root: статическое поле
// Живёт столько же, сколько загружен класс
}
}
4. Объекты в активных потоках (Thread Objects)
fun main() {
val thread = Thread { // Thread — GC Root
val data = loadData() // data тоже GC Root пока работает поток
process(data)
}
thread.start()
// Thread и всё, что он удерживает, не будут удалены
}
5. Классы, загруженные boot class loader (Class Objects)
// Все классы, загруженные из Java/Android framework, всегда доступны
val intClass = Int::class // GC Root: сам класс
val stringClass = String::class // GC Root: сам класс
Графическое представление GC Roots
class User(val name: String, val posts: List<Post>)
class Post(val title: String)
fun main() {
// GC Root 1: локальная переменная user
val user = User("John", listOf(Post("Hello")))
// GC Root 2: локальная переменная name
val name = "Alice"
// GC Root 3: статическое поле
UserRepository.cache[1] = User("Bob", emptyList())
// Граф памяти:
// GC Roots
// ├── user → User("John") → [Post("Hello")]
// ├── name → "Alice"
// └── UserRepository.cache → Map → User("Bob")
}
// После выхода из main():
// GC Roots
// └── UserRepository.cache → Map → User("Bob")
//
// User("John"), "Alice", Post("Hello") будут удалены на следующей GC
Memory Leaks через GC Roots
❌ Утечка памяти: статическое поле удерживает ссылку
class ImageCache {
companion object {
private val cache = mutableMapOf<String, Bitmap>()
// GC Root для всех добавленных Bitmap!
// Они никогда не будут удалены!
}
fun add(key: String, bitmap: Bitmap) {
cache[key] = bitmap // Bitmap живёт столько же, сколько приложение
}
}
fun main() {
val cache = ImageCache()
// Добавляем 100 огромных изображений
repeat(100) { i ->
cache.add("image_$i", loadLargeBitmap())
// Никогда не будут удалены! Утечка памяти
}
}
✅ Правильно:限ное кеширование
class ImageCache {
// WeakReference не препятствует GC
private val cache = WeakHashMap<String, Bitmap>()
fun add(key: String, bitmap: Bitmap) {
cache[key] = bitmap // Удалится при нехватке памяти
}
}
Best Practices для избежания GC Roots утечек
1. Отменяйте слушатели:
class MyActivity : AppCompatActivity() {
private val listener = { data: String ->
println(data) // Это lambda удерживает ссылку на Activity!
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
EventBus.register(listener) // GC Root: Activity удерживается
}
override fun onDestroy() {
super.onDestroy()
EventBus.unregister(listener) // ОБЯЗАТЕЛЬНО отписать!
}
}
2. Очищайте статические кеши:
class AppManager {
companion object {
private val userCache = mutableMapOf<Int, User>()
fun clear() {
userCache.clear() // Освободить память
}
}
}
fun cleanupApp() {
AppManager.clear() // Вызывать при выходе из приложения
}
3. Используйте WeakReference для кешей:
val bitmapCache: MutableMap<String, WeakReference<Bitmap>> = mutableMapOf()
fun cacheImage(key: String, bitmap: Bitmap) {
bitmapCache[key] = WeakReference(bitmap) // Удалится при нехватке памяти
}
fun getImage(key: String): Bitmap? {
return bitmapCache[key]?.get() // Может вернуть null, если GC удалил
}
4. Обнулляйте ссылки при завершении:
class DatabaseConnection {
private var callback: ((String) -> Unit)? = null // GC Root
fun setCallback(cb: ((String) -> Unit)?) {
this.callback = cb
}
fun close() {
callback = null // Освобождаем ссылку перед удалением
}
}
Инструменты для анализа GC Roots
Android Profiler — Heap Dump:
// 1. Открыть Profiler в Android Studio
// 2. Capture Heap Dump
// 3. В MAT (Memory Analyzer Tool) найти Retained Objects
// 4. Проверить GC Root Path
LeakCanary — автоматическое обнаружение утечек:
dependencies {
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.12'
}
Понимание GC Roots критично для написания эффективного кода без утечек памяти в Android.