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

Какие знaешь GC Roots

2.7 Senior🔥 231 комментариев
#JVM и память#Производительность и оптимизация

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

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

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

GC Roots в Java/Android

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

Основная концепция

GC работает по принципу reachability (досягаемость):

  • Если объект достижим из GC Root — он жив
  • Если объект недостижим ни из какого Root — он мертв и может быть удален
class User(val name: String)

fun main() {
    val user = User("John")  // user это GC Root (локальная переменная)
    // user достижим из user, поэтому объект User живет
    
    // Конец функции — user выходит из области видимости
    // user больше не является GC Root
    // Объект User больше не достижим → Garbage Collector удалит его
}

Типы GC Roots

1. Stack Local переменные

Локальные переменные в методах — самый распространенный GC Root.

fun example() {
    val myList = mutableListOf<String>()  // GC Root
    myList.add("item")
    // myList в памяти, пока функция выполняется
    // После выхода из функции — больше не Root
}

class Activity : AppCompatActivity() {
    private var cache: HashMap<String, Data>? = null  // GC Root (property)
    
    fun loadData() {
        val tempList = mutableListOf<String>()  // GC Root (локальная переменная)
        // tempList может быть удалена когда функция завершится
    }
}

2. Static переменные (класса)

Static поля существуют столько же, сколько класс загружен в памяти.

class Config {
    companion object {
        val instance = Config()  // GC Root! Существует всю жизнь приложения
        val cache = mutableMapOf<String, Data>()  // GC Root! Может привести к утечке
    }
}

// Это создает утечку памяти:
companion object {
    val listeners = mutableListOf<OnClickListener>()  // GC Root
}

fun registerListener(listener: OnClickListener) {
    listeners.add(listener)  // listener не удалится никогда!
    // Даже если Activity уничтожена, listener остается в static списке
}

3. Active Thread

Потоки которые находятся в работе — это GC Roots.

fun startThread() {
    val data = mutableListOf<String>()
    
    Thread {
        // data это локальная переменная метода
        // Но пока Thread работает — data остается GC Root!
        while (isRunning) {
            data.add(fetchData())
        }
    }.start()
    
    // Если Thread бесконечный — data никогда не удалится
}

// Более безопасно:
val coroutineScope = CoroutineScope(Dispatchers.Default)
val data = mutableListOf<String>()

fun loadData() {
    coroutineScope.launch {
        // Можно отменить корутину
        data.add(fetchData())
    }
}

fun cleanup() {
    coroutineScope.cancel()  // Отменяем все корутины
}

4. JNI References

Ссылки из JNI (Java Native Interface) код.

// Если native code (C/C++) удерживает ссылку на Java объект
external fun nativeFunction(): Unit

class MyData(val value: String)

fun example() {
    val data = MyData("important")
    nativeFunction()  // native код может удержать ссылку на data
    // Даже когда data выходит из области видимости
    // Она может быть GC Root из JNI
}

Практический пример: утечки памяти

// ПЛОХО — утечка памяти
class MainActivity : AppCompatActivity() {
    companion object {
        private val activities = mutableListOf<MainActivity>()
    }
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        activities.add(this)  // Activity в static списке
        // Когда Activity уничтожена, она не удалится!
    }
}

// ХОРОШО — правильное управление
class MainActivity : AppCompatActivity() {
    private var listener: View.OnClickListener? = null
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        listener = View.OnClickListener {
            // слабая ссылка через WeakReference
        }
    }
    
    override fun onDestroy() {
        super.onDestroy()
        listener = null  // Освобождаем ссылку
    }
}

Виды ссылок и GC

// 1. Strong Reference (сильная) — GC Root
val obj = MyClass()  // Сильная ссылка

// 2. Weak Reference (слабая) — НЕ GC Root
val weakRef = WeakReference(obj)
// Если нет других сильных ссылок — obj может быть удален

// 3. Soft Reference (мягкая) — GC Root если памяти достаточно
val softRef = SoftReference(obj)
// obj удалится только если памяти критически не хватает

// 4. Phantom Reference (фантомная) — служебная
val phantomRef = PhantomReference(obj, referenceQueue)

// Практический пример
class Cache<K, V> {
    private val weakCache = WeakHashMap<K, WeakReference<V>>()
    
    fun put(key: K, value: V) {
        weakCache[key] = WeakReference(value)
    }
    
    fun get(key: K): V? {
        return weakCache[key]?.get()  // может вернуть null если удалено
    }
}

GC Roots в Android Activity

class UserActivity : AppCompatActivity() {
    // 1. this (Activity) это GC Root пока onCreate до onDestroy
    private var adapter: UserAdapter? = null
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_user)
        
        // 2. adapter это локальная переменная (GC Root)
        adapter = UserAdapter(this)  // ОПАСНО — передаем this
        
        // 3. callback удерживает ссылку на Activity
        val button = findViewById<Button>(R.id.btn)
        button.setOnClickListener {
            // this (Activity) в closure — GC Root!
            navigateToDetail()
        }
    }
    
    // УТЕЧКА — inner class удерживает ссылку на Activity
    inner class UserAdapter(val activity: Activity) {
        // activity это GC Root даже когда Activity уничтожена
    }
    
    // РЕШЕНИЕ — static inner class
    private class UserAdapter(val activity: Activity) {
        // или WeakReference
    }
}

Предотвращение утечек памяти

// 1. Используй View Binding вместо findViewById
class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
    }
    
    override fun onDestroy() {
        super.onDestroy()\n        binding = null  // Освобождаем
    }
}

// 2. Отписывайся от listeners
class MyActivity : AppCompatActivity() {
    private val locationListener = LocationListener { /* ... */ }
    
    override fun onResume() {
        super.onResume()
        locationManager.requestLocationUpdates(locationListener)
    }
    
    override fun onPause() {
        super.onPause()
        locationManager.removeUpdates(locationListener)  // Отписываемся!
    }
}

// 3. Используй неанонимные inner class или static
class MyService : Service() {
    // ПЛОХО — Handler в anon inner class
    private val handler = object : Handler(Looper.getMainLooper()) {
        override fun handleMessage(msg: Message) {
            // this удерживает Service
        }
    }
    
    // ХОРОШО — static inner class или WeakReference
    private class MyHandler(val service: WeakReference<MyService>) : 
        Handler(Looper.getMainLooper()) {
        override fun handleMessage(msg: Message) {
            service.get()?.doSomething()
        }
    }
}

Выявление утечек

// Использовать LeakCanary для отладки
dependencies {
    debugImplementation com.squareup.leakcanary:leakcanary-android:2.12
}

// LeakCanary автоматически обнаружит утечки
// и покажет в notification

Итог

GC Roots это:

  • Локальные переменные (Stack variables)
  • Static переменные (Class variables)
  • Активные потоки (Running threads)
  • JNI ссылки

Для предотвращения утечек:

  1. Не храни Activity в static полях
  2. Отписывайся от listeners
  3. Используй WeakReference для долгоживущих объектов
  4. Обнуляй ссылки в onDestroy
  5. Используй LeakCanary для отладки
  6. Будь осторожен с inner class которые удерживают Activity

Понимание GC Roots критично для написания приложений без утечек памяти, особенно в долгоживущих сервисах и Activity.