Это не столько Фьючерсы, которые возвращаются, это ваша проблема. Проблема заключается в том, что для каждого представляемого Runnable ExecutorService будет хранить их для последующей обработки. Каждый раз, когда вы вызываете метод submit с помощью Runnable (или Future), ExecutorService отправляет этот runnable в рабочую очередь. Runnable будет сидеть там до тех пор, пока Поток не сможет выбрать этот Runnable из очереди (позднее). Если все рабочие потоки заняты, то ExecutorService просто поместит исполняемый файл в указанную очередь.
Итак, ваша проблема в том, что у вас есть только один поток, пытающийся получить очередь, которая бесконечно добавляется другим потоком. Он добавляется гораздо быстрее, чем рабочий поток может обрабатывать каждый Runnable.
Редактировать: Пример кода, который я привел в качестве исключений, на самом деле генерирует исключение RejectedExecutionException, поэтому механизм регулирования должен был бы немного отличаться, если бы вы выбрали.
Что касается лучшего решения, как я упоминал в своем комментарии; Если вы ожидаете заполнить ExecutorService таким образом, чтобы рабочие потоки не могли справиться с очередью, вы можете сериализовать и десериализовать запросы по мере их поступления (создавая свой собственный ThreadPoolExecutor), но я убедился бы в необходимости такого дело абсолютно необходимо.
Имейте в виду, что после того, как работа будет сделана, будущее будет сброшено, а мусор будет собран. Так что если вы делаете одно Future в секунду, и оно выполняется менее чем за секунду, само Future будет удалено, и у вас не будет проблем с памятью. Но если вы делаете одно Future в секунду, а потоки делают Future каждые 3 секунды, это будет рисовать и выдавать.
Редактировать: Я профилировал кучу программы, которую вы запускаете, и проблема именно в этом. FutureTask, создаваемый ExecutorService, находится в рабочей очереди, пока рабочий поток не выберет его
Class Name | Shallow Heap | Retained Heap | Percentage
------------------------------------------------------------------------------------------------------------
java.util.concurrent.ThreadPoolExecutor @ 0x78513c5a0 | 104 | 2,051,298,872 | 99.99%
|- java.util.concurrent.LinkedBlockingQueue @ 0x785140598 | 80 | 2,051,298,216 | 99.99%
| |- java.util.concurrent.LinkedBlockingQueue$Node @ 0x785142dd8| 32 | 2,051,297,696 | 99.99%
------------------------------------------------------------------------------------------------------------
Анализ кучи продолжается, существует множество LinkedBlockingQueue $ Node, которые вы можете себе представить