Почему нестатические внутренние классы в Java могут приводить к утечкам памяти
Что проверяют
Знаете ли вы про неявную ссылку на внешний экземпляр и умеете ли связать её с конкретным сценарием утечки. Заодно смотрят, не считаете ли вы сам факт наличия внутреннего класса проблемой: разница между «держит ссылку» и «течёт» тут ключевая.
Как отвечать
Нестатический вложенный класс, а также анонимный и локальный, получает скрытое поле со ссылкой на экземпляр внешнего класса. Именно поэтому из него видны приватные поля внешнего объекта. Пока жив объект внутреннего класса, жив и внешний со всем, что он за собой тянет.
Само по себе это не утечка. Если время жизни внутреннего объекта короче, как у итератора коллекции, всё соберётся штатно. Проблема появляется, когда ссылка переживает владельца: слушатель, зарегистрированный в долгоживущей шине событий или синглтоне и не отписанный, задача, отданная в пул потоков или таймер, колбэк, сохранённый в кэше. Через скрытую ссылку удерживается весь внешний объект целиком, даже если из него использовалось одно поле.
Лечится это так: объявить вложенный класс static и передать внутрь только нужные данные, для долгоживущих подписок хранить слабую ссылку и обязательно отписываться в парном методе жизненного цикла. Полезно знать и про лямбды: лямбда, не обращающаяся к полям и к this, внешний экземпляр не захватывает вовсе, а обращающаяся ведёт себя как анонимный класс.
Где спотыкаются
Считают анонимный класс более безопасным, чем внутренний. Это тот же нестатический вложенный класс, просто без имени, и ссылку на окружающий объект он держит точно так же.
Уверены, что лямбда никогда не удерживает внешний объект. Правило работает только пока лямбда ничего не берёт из экземпляра: любое обращение к полю или к методу объекта захватывает весь объект.
Ищут утечку чтением исходников. Подтверждается она дампом кучи и цепочкой ссылок до корня сборщика мусора: там сразу видно, кто именно держит объект, который давно должен был умереть.