Похоже, у вас есть две разные проблемы:
1) Вы перегружаете рабочую очередь. Вы не можете просто добавлять новые задачи в очередь, не обращая внимания на уровень потребления исполнителей задач. Вам нужно разобраться в логике, чтобы знать, когда нужно блокировать новые добавления в рабочей очереди.
2) Любое неперехваченное исключение в потоке задачи может полностью убить поток. Когда это происходит, ExecutorService запускает новый поток для его замены. Но это не значит, что вы можете игнорировать любую проблему, которая может привести к потере потока! Найдите эти необъяснимые исключения и поймайте их!
Это всего лишь догадка (потому что в вашем сообщении недостаточно информации, чтобы знать иначе), но я не думаю, что ваша проблема в том, что исполнитель задач останавливает обработку задач. Я предполагаю, что он просто не обрабатывает задачи так быстро, как вы их создаете. (И тот факт, что ваши задачи иногда умирают преждевременно, вероятно, ортогональн к проблеме.)
По крайней мере, таков мой опыт работы с пулами потоков и исполнителями задач.
Хорошо, вот еще одна возможность, которая звучит выполнимо на основе вашего комментария (что все будет работать гладко в течение нескольких часов, пока внезапно не остановится) *
У вас может быть редкий тупик между потоками задач. В большинстве случаев вам везет, и тупик не проявляется. Но иногда два или более ваших потоков задач попадают в состояние, в котором они ожидают снятия блокировки, удерживаемой другим потоком. В этот момент больше не может выполняться обработка задач, и ваша рабочая очередь будет накапливаться до тех пор, пока вы не получите OutOfMemoryError.
Вот как я бы диагностировал эту проблему:
Устранить ВСЕ разделяемое состояние между потоками задач. Во-первых, для этого может потребоваться, чтобы каждый поток задач создавал защитную копию всех необходимых им общих структур данных. После того, как вы это сделаете, совершенно невозможно будет зайти в тупик.
В этот момент постепенно вводятся общие структуры данных, по одной (с соответствующей синхронизацией). Перезапустите ваше приложение после каждой крошечной модификации, чтобы проверить тупик. Когда вы снова столкнетесь с этой аварийной ситуацией, внимательно изучите шаблоны доступа к общему ресурсу и определите, действительно ли вам нужно делиться им.
Что касается меня, всякий раз, когда я пишу код, который обрабатывает параллельные задачи с пулами потоков и исполнителями, я всегда стараюсь исключить общее состояние ALL между этими задачами. Что касается приложения, они также могут быть полностью автономными приложениями. Выискивание взаимоблокировок - это перетаскивание, и, по моему опыту, лучший способ устранить взаимоблокировки состоит в том, чтобы каждый поток имел свое собственное локальное состояние, а не делил какое-либо состояние с другими потоками задач.
Удачи!