Как обеспечить сборку мусора FutureTask, которая отправляется ThreadPoolExecutor, а затем отменяется?

Я отправляю Callable объекты в ThreadPoolExecutor, и кажется, что они застряли в памяти.

Посмотрев дамп кучи с помощью инструмента MAT для Eclipse, вы увидите, что на объекты Callable ссылается вызываемая переменная FutureTask$Sync. На этот FutureTask$Sync ссылается переменная sync FutureTask. На этот FutureTask ссылается переменная this $ 0 FutureTask$Sync.

Я читал об этом (здесь, здесь и на SO) и похоже FutureTask, в который вызываемый объект заключен в ThreadPoolExecutor submit (), навсегда хранит ссылку на вызываемый объект.

Что меня смущает, так это как гарантировать, что FutureTask получит сборщик мусора, чтобы он не продолжал удерживать вызываемый объект в памяти и удерживать все, что вызываемый объект может удерживать в памяти?

Чтобы предоставить более подробную информацию о моей конкретной ситуации, я пытаюсь реализовать ThreadPoolExecutor таким образом, чтобы при необходимости можно было отменить все отправленные задачи. Я пробовал несколько разных методов, которые нашел на SO и в других местах, например, полностью выключил исполнителя (с shutdown(), shutdownNow() и т. Д.), А также сохранил список фьючерсов, возвращаемых submit(), и вызвал отмену для всех, а затем очистил список фьючерсы. В идеале я бы хотел не выключать его, а просто cancel() и убирать его, когда это необходимо.

Кажется, что все эти методы не имеют значения. Если я отправлю вызываемый объект в пул, велика вероятность, что он останется.

Что я делаю неправильно?

Спасибо.

Изменить:

По запросу вот конструктор ThreadPoolExecutor.

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
    super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
}

После дальнейшего тестирования я вижу, что если я позволю задачам, которые были отправлены в ThreadPoolExecutor, завершиться, то утечки не будет. Если я попытаюсь отменить их каким-либо образом, например:

shutdownNow()

Или сохраните ссылку на будущее и вызовите отмену для нее позже:

Future referenceToCancelLater = submit(task);
...
referenceToCancelLater.cancel(false);

Или удалив их из очереди такими методами, как:

getQueue.drainTo(someList)

or

getQueue.clear()

или Цикл по сохраненным ссылкам на фьючерсы и вызов:

getQueue.remove(task)

Любой из этих случаев приводит к тому, что FutureTask остается, как описано выше.

Итак, реальный вопрос во всем этом заключается в том, как правильно отменить или удалить элементы из ThreadPoolExecutor, чтобы FutureTask собирал мусор и не просачивался навсегда?


person cottonBallPaws    schedule 06.02.2011    source источник
comment
опубликуйте свой код с помощью жесткого способа удаления: ThreadPoolExecutor.getQueue().remove(future) сделает свое дело   -  person bestsss    schedule 07.02.2011
comment
Существует некоторая дискуссия о том, следует ли использовать getQueue () таким образом. Есть ли в этом действительно недостаток?   -  person cottonBallPaws    schedule 07.02.2011
comment
@bestsss Я надеялся, что меня не спросят об этом;) В коде много чего происходит, и я не уверен, какие части имеют отношение к этой проблеме. Я надеялся получить общее представление о том, что могло вызвать утечку в отношении FutureTask. Есть ли конкретная часть, которую вы хотите увидеть?   -  person cottonBallPaws    schedule 07.02.2011
comment
@littleFluffyKitty, поскольку вы можете создать очередь сама (а у меня есть странные очереди с политикой планирования потоков стека), вы можете считать ее частью ThreadPoolExecutor API, использовать ее по своему усмотрению (опасность). ThreadPoolExecutor использует такую ​​очередь, например, в shutdownNow (). Опять же, ваша проблема не в очереди. Вы сливаете что-л. Где-то еще   -  person bestsss    schedule 07.02.2011
comment
@littleFluffyKitty, что касается кода. jmap -histogram, чтобы убедиться, что у вас действительно много фьючерсов, а не только Runnable / Callables, вы можете переопределить decorateTask, чтобы убедиться, что Futures являются вашими собственными (и легко различимы для остальной части FutureTask, вы хотите просто расширить FutureTask и этого было бы достаточно). Затем код, который вы создаете, ThreadPool, где вы вызываете submit.   -  person bestsss    schedule 07.02.2011
comment
@bestsss, глядя на гистограмму, показывает, что существует такое же количество экземпляров FutureTask и FutureTask $ Sync, сколько вызываемых объектов все еще находится в памяти. Также похоже (по крайней мере, на данный момент ...), что те, которые застревают в памяти, - это те, которые были отменены. Возможно, я попробую упомянутый вами метод getQueue (). Remove () и посмотрю, сохраняет ли он их в памяти. Поскольку кажется, что отмена не обрабатывается правильно.   -  person cottonBallPaws    schedule 07.02.2011
comment
@littleFluffyKitty покажет c-tor этого ThreadPoolExecutor, как вы создаете, какую очередь вы используете и отдыхаете, есть ли у вас ЛЮБОЙ поток для обработки отмененных фьючерсов, они должны быть удалены из очереди, когда придет их время, что для запланированная очередь исполнителя может быть далеко по времени.   -  person bestsss    schedule 07.02.2011
comment
@bestsss, обновил вопрос с конструктором. Я не совсем понимаю, что вы имеете в виду под своим последним комментарием.   -  person cottonBallPaws    schedule 07.02.2011
comment
PauseCancelThreadPoolExecutor, что это? Вы не используете ThreadPoolExecutor. Покажите реализацию класса. Поскольку Queue.clear () не удаляет ссылки, они хранятся где-то в этом загадочном классе - PauseCancelThreadPoolExecutor.   -  person bestsss    schedule 07.02.2011
comment
@littleFluffyKitty (я вижу, вы вернулись к ThreadPoolExecutor), поэтому последний вопрос: где вы храните referenceToCancelLater, вы также должны удалить его из этой структуры.   -  person bestsss    schedule 07.02.2011
comment
@bestsss, извините за радиомолчание. Я пробовал несколько разных способов и всегда удалял ссылку из массива, в котором она находилась. На этом этапе я только что прибег к установке флага в вызываемом объекте, который я могу установить на отмену, и перебрал и установил их, когда я хочу отменить их все. Потом они просто сами выбрасываются из очереди. Это не очень хорошо, но пока вроде работает.   -  person cottonBallPaws    schedule 08.02.2011
comment
Эта проблема была зарегистрирована как ошибка JDK 6602600 (bugs.sun.com/bugdatabase/view_bug .do? bug_id = 6602600).   -  person Flavio    schedule 14.10.2012


Ответы (4)


Согласно этому сообщению, вы можете позвонить по очистить исполнителя.

person gsingh2011    schedule 16.12.2012
comment
он говорит: «Пытается удалить из рабочей очереди все будущие задачи, которые были отменены». Этот метод может быть полезен в качестве операции восстановления хранилища, которая не оказывает никакого другого влияния на функциональность. Отмененные задачи никогда не выполняются, но могут накапливаться в рабочих очередях, пока рабочие потоки не смогут их активно удалить. Вместо этого вызов этого метода пытается удалить их сейчас. Однако этот метод может не удалить задачи при наличии вмешательства со стороны других потоков. Означает ли это, что если задача уже запущена, но зашла в тупик, она не будет убита? - person HoaPhan; 07.03.2018

В качестве обходного пути вы могли бы сделать что-нибудь вроде:

class ClearingCallable<T> implements Callable<T> {
    Callable<T> delegate;
    ClearingCallable(Callable<T> delegate) {
        this.delegate = delegate;
    }

    T call() {
        try {
            return delegate.call();
        } finally {
            delegate = null;
        }
    }
}
person Tom    schedule 07.02.2011
comment
Что было бы делегатом в этом случае? - person cottonBallPaws; 07.02.2011
comment
Ваш вызываемый объект, который удерживает состояние, в котором вы хотите собрать мусор. - person Tom; 07.02.2011
comment
это ничего не решает. Проблема не в ThreadPoolExecutor, т.к. он сам по себе не пропускает выполненные задачи - person bestsss; 07.02.2011
comment
Это FutureTask, который содержит вызываемый объект, который я хочу собрать сборщиком мусора. Глядя на кучу, я не могу понять, почему она не собирается. - person cottonBallPaws; 07.02.2011
comment
Итак, проблема в том, что ваш возвращенный объект удерживается? Таким образом, альтернативным заклеиванием трещин будет просто возврат, скажем WeakReference и затем вызов clear на нем. Конечно, у вас все еще протекает небольшой предмет. - person Tom; 07.02.2011

Я не мог заставить ничего работать, поэтому я придумал следующее решение. Вот приблизительный обзор: я создал массив в ThreadPoolExecutor, который отслеживал запускаемые объекты, находящиеся в очереди. Затем, когда мне нужно было отменить очередь, я перебирал и вызывал метод отмены для каждого из запускаемых объектов. В моем случае все эти runnables были созданным мной настраиваемым классом, а их метод отмены просто устанавливал флаг отмены. Когда очередь вызывала следующий для обработки, при запуске runnable он видел, что он был отменен, и пропускал фактическую работу.

Таким образом, все runnables затем быстро удаляются один за другим, поскольку он видит, что он был отменен.

Возможно, не самое лучшее решение, но оно работает для меня и не пропускает память.

person cottonBallPaws    schedule 07.03.2011
comment
Привет, я думаю, у меня такая же проблема, как и у вас. Вы придумали, как чище решить проблему;) способом? На самом деле должны существовать некоторые классы Java, которые позаботятся об удалении этих просроченных потоков. Ваше здоровье - person Simone; 13.06.2012
comment
@Simone, к сожалению, нет, я не смог найти что-то еще встроенное. Я все еще использую вышеуказанный метод. Однако он работает отлично, просто потребовались некоторые усилия, чтобы его настроить. - person cottonBallPaws; 13.06.2012
comment
Спасибо за ответ. Затем, освобождает ли метод cancel (класс Runnable) выделенную для потока память или вы сделали это вручную? - person Simone; 14.06.2012

См.: https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Future.html.

Будущее представляет собой результат асинхронного вычисления. Если результат не может быть получен с помощью метода get, произойдет утечка памяти!

Если вы не хотите получать асинхронный результат, используйте Runnable, установите Callable.

person zhenjing    schedule 05.07.2016