GC roots и достижимость
Сборщик считает объект живым, если до него есть путь по ссылкам от какого-то GC root. Корни — это локальные переменные и операнды в стеках живых потоков, статические поля загруженных классов, JNI-ссылки, активные мониторы и т.п.
Всё, до чего нельзя дойти от корней, — мусор. Поэтому «утечка памяти» в Java — это не недостижимый объект (он соберётся), а наоборот: объект, который всё ещё достижим от корня, хотя уже не нужен.

Четыре типа ссылок
Strong — обычная ссылка: пока она есть, объект не собирается. Soft (SoftReference) — объект доживёт до нехватки памяти, потом будет собран; подходит для кэшей, которые не жалко сбросить под давлением. Weak (WeakReference) — объект собирается при первой же сборке, если на него нет сильных ссылок; основа WeakHashMap и канонизирующих кэшей. Phantom (PhantomReference) — не даёт доступа к объекту, лишь сигнализирует, что он уже финализирован и память вот-вот освободится.
Слабые и фантомные ссылки регистрируют в ReferenceQueue, чтобы узнавать момент их очистки и выполнять пострешур.
Map<Key, Heavy> cache = new WeakHashMap<>();
// запись исчезнет автоматически, когда на Key не останется сильных ссылок
Cleaner вместо finalize
finalize() устарел и опасен: момент вызова не гарантирован, он тормозит сборку, может «воскресить» объект и проглатывает исключения. В современном коде его не используют.
Для освобождения нативных ресурсов применяют java.lang.ref.Cleaner (на основе PhantomReference + ReferenceQueue), но как страховку. Основной способ детерминированно освобождать ресурсы — AutoCloseable + try-with-resources, а не финализация.
Утечки через сильные ссылки
Типичные источники утечек: объекты, забытые в долгоживущей коллекции или статике; не отписанные слушатели (listener держит подписчика); ThreadLocal, не очищенный в пуле потоков (поток живёт долго и держит значение); ключи в обычной HashMap, которые «должны были» исчезнуть.
Диагностика — heap dump и анализ доминаторов/путей до GC root: инструмент показывает, какая сильная цепочка не даёт объекту умереть.
Как отвечать на собеседовании
Объясните reachability от GC roots и переопределите «утечку» как достижимый-но-ненужный объект. Разложите strong/soft/weak/phantom с практикой (Soft — кэш, Weak — WeakHashMap, Phantom/Cleaner — пострешур ресурсов), скажите, что finalize устарел в пользу try-with-resources/Cleaner, и назовите классические источники утечек (ThreadLocal в пуле, не отписанные listeners).