Содержит ли будущий объект, возвращаемый executorService.submit(Runnable), какую-либо ссылку на исполняемый объект?

Предположим, у нас есть следующий код:

List<Future<?>> runningTasks;
ExecutorService executor;
...
void executeTask(Runnable task){
    runningTasks.add(executor.submit(task));
}

Мои вопросы:

  1. Содержит ли runningTasks ссылку на объект task?
  2. Как долго он его держит? Он все еще удерживает его после завершения задачи?
  3. Чтобы избежать утечек памяти, нужно ли мне позаботиться об удалении будущего, добавленного в список?

person Daniel Rusev    schedule 11.04.2014    source источник
comment
Обычно да. Пока задача выполняется, это не будет иметь значения, так как на нее все равно ссылается выполняющийся поток. И после его завершения я бы просто удалил Future из списка, который называется runningTasks  -  person Holger    schedule 11.04.2014
comment
Могу ли я вместо этого составить список для хранения слабых ссылок? Что-то вроде List‹WeakReference‹Future‹?›››?   -  person Daniel Rusev    schedule 11.04.2014
comment
Ты можешь сделать. Но это заставило бы меня задуматься, почему вы вообще храните Future в списке.   -  person Holger    schedule 11.04.2014
comment
Потому что в определенный момент мне нужно отменить определенные задачи.   -  person Daniel Rusev    schedule 11.04.2014
comment
Тогда List<WeakReference<Future<?>>> будет работать. Это позволит собрать Future, но вам придется вручную удалить экземпляр WeakReference (хотя сам WeakReference не занимает много места). Альтернативой может быть Collections.newSetFromMap(new WeakHashMap<Future<?>,Boolean>()) для создания Set<Future<?>>, который позволяет собирать его элементы. Не может быть проще…   -  person Holger    schedule 11.04.2014


Ответы (2)


До тех пор, пока исполнитель или объект Future не содержат ссылку на него, является деталью реализации. Поэтому, если ваши задачи используют много памяти, так что вам приходится беспокоиться об использовании памяти, вы должны явно выполнить очистку в своей задаче до завершения задачи.

Если вы посмотрите на исходный код OpenJDK 1.6 для ThreadPoolExecutor, вы можете видеть, что на самом деле базовый объект Future фактически сохраняет ссылку на базовый вызываемый объект на неопределенный срок (т.е. до тех пор, пока поскольку существует сильная ссылка на объект Future, вызываемый объект не может быть GCd). Это верно и для 1.7. Начиная с версии 1.8 ссылка на него обнуляется. Однако вы не можете контролировать, на какой реализации ExecutorService будет выполняться ваша задача.

Использование WeakReference должно работать на практике как Future, и, таким образом, объект Callable может быть GCd после выполнения задачи, а разумные реализации ExecutorService должны терять ссылку на него, когда задача выполнена. Строго говоря, это все еще зависит от реализации ExecutorService. Кроме того, использование WeakReference может привести к неожиданно большим накладным расходам. Гораздо лучше просто явно очистить любой объект, который занимает много памяти. И наоборот, если вы не выделяете большие объекты, я бы не беспокоился.

Конечно, это обсуждение полностью отличается от утечки памяти, которая у вас будет, если вы продолжите добавлять фьючерсы в свой список, никогда не удаляя их. Если вы сделаете это, даже использование WeakReference не поможет; у вас все равно будет утечка памяти. Для этого просто пройдитесь по списку и удалите фьючерсы, которые уже сделаны и поэтому бесполезны. Это действительно хорошо делать это каждый раз, если у вас нет смехотворно большого размера очереди, так как это очень быстро.

person Enno Shioji    schedule 16.07.2014
comment
В ThreadPoolExecutor я нигде не вижу, чтобы объект Future сохранял ссылку на базовый вызываемый объект на неопределенный срок. - person Vipin; 16.07.2014
comment
@Vipin: ThreadPoolExecutor возвращает экземпляр FutureTask как Future. Эта реализация FutureTask, в свою очередь, имеет поле FutureTask.Sync.callable, которое указывает на базовый вызываемый объект. И поле, содержащее объект синхронизации, и его поле, указывающее на базовый вызываемый объект, являются окончательными. См. -› grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/ - person Enno Shioji; 16.07.2014
comment
в JDK 7 он хранит ссылку в вызываемой ссылочной переменной FutureTask, но если вы посмотрите на метод FutureTask.finishCompletion(), он устанавливается как нуль. и finishCompletetion вызывается как в cancel(), так и в run(), поэтому в конечном итоге он становится нулевым и не возникает опасности утечки памяти. - person Vipin; 17.07.2014
comment
@Vipin: О каком JDK ты говоришь? Я смотрю на OpenJDK, может быть, вы смотрите на Oracle JDK или IBM JDK? Кроме того, он мог измениться в второстепенной версии или измениться в будущих версиях, таких как JDK 9. А что происходит с другими службами ExecutorServices, такими как MemoryAwareExecutor от Netty или ListeningExecutorservice от Guava? Вы не можете знать, как ведет себя вся реализация. Поэтому не стоит полагаться на совпадение реализации. - person Enno Shioji; 17.07.2014
comment
Я привел только пример реализации FutureTask, написанный Дугом Ли и командой. Такая же реализация используется в OpenJDK и Oracle JDK. Я использую Oracle JDK 1.7.0_60. - person Vipin; 17.07.2014

  1. Содержит ли runningTasks(Future) ссылку на объект задачи? --- Да
  2. Как долго он его держит? Он все еще удерживает его после завершения задачи? --- Он хранит ссылку на задачу до тех пор, пока задача не будет завершена, после чего она удаляется из очереди ThreadPoolExecutor, также в методе FutureTask.finishCompletion() мы устанавливаем ссылку на вызываемую (задачу) в ноль. FutureTask.finishCompletion() вызывается внутри методов запуска и отмены FuturetTask. Таким образом, при выполнении и отмене в обоих случаях будущее не имеет ссылки на задачу.
  3. Чтобы избежать утечек памяти, нужно ли мне позаботиться об удалении будущего, добавленного в список? --- Вы в безопасности, если используете Future.

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

Для получения более подробной информации с примером ситуации с утечкой памяти и ее решением см. мой другой ответить.

person Vipin    schedule 16.07.2014
comment
Я не думаю, что это правильно. По крайней мере, в OpenJDK 1.6 объект Future, созданный ThreadPoolExecutor, сохраняет ссылку на базовый вызываемый объект даже после выполнения задачи. - person Enno Shioji; 16.07.2014
comment
@EnnoShioji посмотрите на метод getTask() класса ThreadPoolExecutor.Worker. Он имеет вызовы poll() и take(), которые извлекают и удаляют начало очереди. - person Vipin; 16.07.2014
comment
Конечно, он удаляется из внутренней очереди, но объект Future, который является FutureTask, по-прежнему имеет поле, которое ссылается на базовый вызываемый объект, как можно увидеть здесь: grepcode.com/file/ репозиторий.grepcode.com/java/root/jdk/openjdk/ - person Enno Shioji; 16.07.2014
comment
Поле FutureTask.Sync.callable - person Enno Shioji; 16.07.2014
comment
В OpenJDK 1.7 он все еще есть, а в 1.8, похоже, его начали обнулять (grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/). Однако именно поэтому вы не хотите полагаться на артефакт реализации. - person Enno Shioji; 16.07.2014
comment
@EnnoShioji в JDK 7 хранит ссылку в вызываемой ссылочной переменной FutureTask, но если вы посмотрите на метод FutureTask.finishCompletion(), он устанавливается как нуль. и finishCompletetion вызывается как в cancel(), так и в run(), поэтому в конечном итоге он становится нулевым и не возникает опасности утечки памяти. - person Vipin; 17.07.2014