← Назад к вопросам
Какие зн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 ссылки
Для предотвращения утечек:
- Не храни Activity в static полях
- Отписывайся от listeners
- Используй WeakReference для долгоживущих объектов
- Обнуляй ссылки в onDestroy
- Используй LeakCanary для отладки
- Будь осторожен с inner class которые удерживают Activity
Понимание GC Roots критично для написания приложений без утечек памяти, особенно в долгоживущих сервисах и Activity.