Может ли запланированное будущее вызвать утечку памяти?

Я думаю, что у меня утечка памяти на моих активных живых обоях Android. Всякий раз, когда я поворачиваю экран, количество собранных мусора памяти увеличивается на 50 кбайт и не возвращается. Я думаю, что это может быть вызвано запланированным будущим, поэтому я собираюсь представить сценарий, чтобы узнать, так ли это.

Допустим, у вас есть класс (назовем его Foo), который имеет следующие члены.

private ScheduledFuture<?> future; private final ScheduledExecutorService scheduler = Executors .newSingleThreadScheduledExecutor(); private final Runnable runnable = new Runnable() { public void run() { // Do stuff } }; 

И теперь вы назначили запланированное будущее

 future = scheduler.scheduleAtFixedRate(runnable, delay, speed, TimeUnit.MILLISECONDS); 

Будущее содержит ссылку на runnable, а runnable содержит ссылку на родительский объект Foo. Я не уверен, что это так, но может ли этот факт означать, что если в программе нет ссылки на Foo, сборщик мусора все равно не сможет его собрать, потому что запланированное будущее? Я не слишком хорошо разбираюсь в многопоточности, поэтому я не знаю, показывает ли код, который я показал, запланированное задание будет жить дольше, чем объект, а это значит, что он не станет собирать мусор.

Если этот сценарий не приведет к тому, что Foo не станет сборкой мусора, мне просто нужно сказать, что с простым объяснением. Если это не мешает Foo собирать мусор, то как его исправить? Нужно делать future.cancel(true); future = null; future.cancel(true); future = null; ? Является ли future = null частью ненужным?

Solutions Collecting From Web of "Может ли запланированное будущее вызвать утечку памяти?"

  • Либо ваш метод run основан на включении класса Foo и, следовательно, не может жить независимо. В этом случае я не вижу, как вы могли бы использовать ваш Foo gc'ed и сохранить ваш runnable «живой», который будет выполняться исполнителем
  • Или ваш метод run статичен в том смысле, что он не зависит от состояния вашего класса Foo , и в этом случае вы можете сделать его статическим, и это предотвратит проблему, с которой вы столкнулись.

Кажется, вы не справляетесь с перерывами в своем Runnable. Это означает, что даже если вы вызываете future.cancel(true) ваш Runnable будет продолжать работать, что, как вы определили, может стать причиной утечки.

Существует несколько способов сделать Runnable «удобным для прерывания». Либо вы вызываете метод, который выдает InterruptedException (например, Thread.sleep() или метод блокировки IO), и он будет Thread.sleep() InterruptedException когда будущее будет отменено. Вы можете поймать это исключение и немедленно выйти из метода запуска после очистки того, что нужно очистить и восстановить прерванное состояние:

 public void run() { while(true) { try { someOperationThatCanBeInterrupted(); } catch (InterruptedException e) { cleanup(); //close files, network connections etc. Thread.currentThread().interrupt(); //restore interrupted status } } } 

Если вы не вызываете таких методов, стандартная идиома:

 public void run() { while(!Thread.currentThread().isInterrupted()) { doYourStuff(); } cleanup(); } 

В этом случае вы должны попытаться убедиться, что состояние в то время проверяется регулярно.

С этими изменениями, когда вы вызываете future.cancel(true) , сигнал прерывания будет отправлен в поток, выполняющий ваш Runnable, который выйдет из того, что он делает, делая ваш Runnable и ваш экземпляр Foo подходящим для GC.

Хотя на этот вопрос дан ответ, но после прочтения этой статьи я подумал о публикации нового ответа с объяснением.

Может ли запланированное будущее вызвать утечку памяти? — ДА

ScheduledFuture.cancel() или Future.cancel() вообще не уведомляет своего Executor том, что он был отменен, и он остается в очереди, пока не наступило время его выполнения. Это не имеет большого значения для простых фьючерсов, но может быть большой проблемой для ScheduledFutures . Он может оставаться там в течение нескольких секунд, минут, часов, дней, недель, лет или почти бесконечно в зависимости от задержек, которые он запланировал.

Вот пример с наихудшим сценарием. Управляемый и все, что он ссылается, останется в очереди на Long.MAX_VALUE миллисекунды даже после того, как его будущее будет отменено!

 public static void main(String[] args) { ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1); Runnable task = new Runnable() { @Override public void run() { System.out.println("Hello World!"); } }; ScheduledFuture future = executor.schedule(task, Long.MAX_VALUE, TimeUnit.MILLISECONDS); future.cancel(true); } 

Вы можете увидеть это с помощью Profiler или вызова метода ScheduledThreadPoolExecutor.shutdownNow() который вернет список с одним элементом в нем (это Runnable, который был отменен).

Решение этой проблемы состоит в том, чтобы либо написать свою собственную реализацию Future, либо время от времени вызывать метод purge() . В случае пользовательского решения завода-изготовителя:

 public static ScheduledThreadPoolExecutor createSingleScheduledExecutor() { final ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1); Runnable task = new Runnable() { @Override public void run() { executor.purge(); } }; executor.scheduleWithFixedDelay(task, 30L, 30L, TimeUnit.SECONDS); return executor; } 

Будущее содержит ссылку на runnable, а runnable содержит ссылку на родительский объект Foo. Я не уверен, что это так, но может ли этот факт означать, что если в программе нет ссылки на Foo, сборщик мусора все равно не сможет его собрать, потому что запланированное будущее?

Его плохая идея сделать Foo своего рода временным объектом, который вы создаете часто, потому что вы должны закрыть ScheduledExecutorService scheduler когда ваше приложение закрывается. Таким образом, вы должны сделать Foo псевдо-синглтон.

Сборщик мусора понимает циклы, поэтому, как только вы завершите работу исполнителя Foo вас, скорее всего, не будет проблем с памятью.