что может привести к постоянному увеличению потока состояния ожидания в Java 8

#java #multithreading #java-8 #concurrency

#java #многопоточность #java-8 #параллелизм

Вопрос:

Сегодня я обнаружил, что в моих приложениях Java 8 многие потоки находятся в состоянии ОЖИДАНИЯ:

 [arthas@1]$ thread --state RUNNABLE
Threads Total: 3427, NEW: 0, RUNNABLE: 17, BLOCKED: 0, WAITING: 3114, TIMED_WAITING: 296, TERMINATED: 0                                                                              
ID             NAME                                         GROUP                          PRIORITY       STATE          %CPU           TIME           INTERRUPTED    DAEMON         
124            pool-11-thread-25                            main                           5              RUNNABLE       75             0:0            false          false          
53             as-command-execute-daemon                    system                         10             RUNNABLE       23             0:0            false          true           
133            Thread-20                                    main                           5              RUNNABLE       1              0:2            false          true           
28             Apollo-RemoteConfigLongPollService-1         Apollo                         5              RUNNABLE       0              0:0            false          true           
32             Attach Listener                              system                         9              RUNNABLE       0              0:0            false          true           
99             DestroyJavaVM                                main                           5              RUNNABLE       0              0:39           false          false          
4              Signal Dispatcher                            system                         9              RUNNABLE       0              0:0            false          true           
19             grpc-default-worker-ELG-1-1                  main                           5              RUNNABLE       0              0:0            false          true           
21             grpc-default-worker-ELG-1-2                  main                           5              RUNNABLE       0              0:0            false          true           
97             http-nio-11003-Acceptor                      main                           5              RUNNABLE       0              0:0            false          true           
85             http-nio-11003-BlockPoller                   main                           5              RUNNABLE       0              0:0            false          true           
96             http-nio-11003-ClientPoller                  main                           5              RUNNABLE       0              0:0            false          true           
54             lettuce-nioEventLoop-4-1                     main                           5              RUNNABLE       0              0:0            false          true           
70             lettuce-nioEventLoop-4-2                     main                           5              RUNNABLE       0              0:0            false          true           
36             nioEventLoopGroup-3-1                        system                         10             RUNNABLE       0              0:0            false          false          
42             nioEventLoopGroup-3-2                        system                         10             RUNNABLE       0              0:0            false          false          
37             nioEventLoopGroup-4-1                        system                         10             RUNNABLE       0              0:0            false          false          
Affect(row-cnt:0) cost in 120 ms.
  

поток более 3000 находится в состоянии ОЖИДАНИЯ, теперь я выбираю случайный поток пула ОЖИДАЮЩИХ потоков, который отображается следующим образом::

 [arthas@1]$ thread 4410
"pool-96-thread-10" Id=4410 WAITING on java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject@3e27c029
    at sun.misc.Unsafe.park(Native Method)
    -  waiting on java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject@3e27c029
    at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
    at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1088)
    at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:809)
    at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:748)

Affect(row-cnt:0) cost in 16 ms.
  

но проблема в том, что я не знаю, с чего начался поток и что заставляет ожидающий поток увеличиваться. Есть ли какой-либо способ узнать, с чего начать поток или почему ОЖИДАЮЩИЙ поток увеличивается? Сейчас я использую Java ThreadExecutor. Прямо сейчас ожидающий поток достигает 6000 . Я добавляю пользовательскую конфигурацию:

 @Configuration
public class ScheduleConfig implements SchedulingConfigurer {

    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        taskRegistrar.setScheduler(Executors.newScheduledThreadPool(30));
    }

}
  

введите описание изображения здесь

Комментарии:

1. Используете ли вы ScheduledExecutorService где-нибудь в своем приложении, настроенном на 3000 потоков, которому не часто приходится запускать задачи?

2. Сейчас я использую расписание cron в своем проекте. @BeUndead

3. Настроен ли он для возможности повторного использования потоков надлежащим образом? Вы дали ему настройку Queue при создании, которая ничего не может содержать и поэтому каждый раз создает новый поток? Похоже, что он постоянно растет каждую минуту. Запланировано ли что-нибудь CRON на каждую минуту, что может приводить к ошибкам и оставлять потоки в непригодном для использования состоянии?

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

5. Я не нашел ни ScheduledThreadPoolExecutor одного класса в своем проекте, но я настраиваю задачу расписания как многопоточную. @Andreas

Ответ №1:

Показанная вами трассировка стека является «нормальной ситуацией»: это поток-исполнитель threadpool, который готов к выполнению работы, но очередь работы пуста. В этом случае «ожидание» означает: я жду выполнения задания, а не: «Мне нужно что-то сделать, но я не могу этого сделать, потому что я жду, когда материал будет завершен первым».

Теперь 3000 потоков сами по себе вызывают некоторую озабоченность; у каждого потока есть свое собственное пространство стека. Насколько это велико, зависит от вашего -Xss параметра, но они, как правило, увеличиваются с 64 КБ до 1 МБ. Если это 1 МБ, это… Это 3 ГБ стекового пространства… неоптимально. Это число (количество потоков, ожидающих принятия задания) также не должно сильно увеличиваться после того, как виртуальная машина разогрелась.

Если все / большинство из этих ОЖИДАЮЩИХ потоков имеют аналогичную трассировку, то на самом деле есть только два варианта:

  • Вы создали исполнителя и продолжаете просить его со временем добавлять все больше и больше потоков. Я сомневаюсь в этом, но это возможно.
  • Вы продолжаете создавать исполнителей. Не делайте этого.

Идея исполнителя заключается в том, что вы создаете только один или, по крайней мере, очень и очень немногие из них.

Если вы ДОЛЖНЫ создавать их как часть вашего запущенного приложения (в отличие от обычной процедуры создания заданий и передачи их исполнителю singleton), то имейте в виду, что они фактически являются ресурсами: если вы не «закроете» их, ваш процесс будет требовать все больше и больше ресурсов, пока в конечном итоге виртуальная машина не будет запущена.сбой при его завершении.

Чтобы закрыть их, вы вызываете shutdown() , который запрашивает красиво, а shutdownNow() который более агрессивен, и все еще не выполненные задания будут отменены навсегда.

Итак, чтобы резюмировать:

  • Вы создаете новых исполнителей во время обычной обработки в своем приложении. Найдите new ScheduledThreadPoolExecutor в своей кодовой базе и проверьте ситуацию. Добавьте некоторое ведение журнала, если вам нужно увидеть это в действии.
  • Тогда, скорее всего, вы хотите исправить это и в первую очередь прекратить создавать новых исполнителей — просто создайте один раз и передайте задания этому исполнителю.
  • Если действительно имеет смысл их создавать, используйте некоторую конструкцию guardian, чтобы убедиться, что вы также очистите их, когда закончите их использовать. Вы можете поискать, как сделать это безопасно; это немного сложно, так как вам нужно решить, что делать с любыми заданиями в очереди, которые еще не выполнены. Если это не проблема, все просто: .shutdown() выполнит работу.

Комментарии:

1. Может ли создание новых исполнителей для каждого нового входящего запроса вызывать проблему даже после вызова shutdown() после обработки запроса?

2. Определите «проблему». Я сомневаюсь в этом.

3. Мы наблюдаем высокое использование встроенной памяти в нашем сервисе. Размер кучи не увеличивается. При проверке потоков в этом контейнере я обнаружил, что около 10 тыс. потоков были созданы как часть новых объектов службы исполнителя. shutodwn() вызывается для этих объектов службы исполнителя, но эти потоки все еще задерживались. Могут ли они привести к увеличению потребления встроенной памяти?

4. Объект завершения Thread работы обычно похож на любой другой объект; Он будет GCed при необходимости. Как правило, процесс Java всегда использует большую память: он использует все это, а затем управляет своей собственной кучей. Я не думаю, что комментарии SO приведут к просветлению здесь, я боюсь — управление памятью Java на несколько порядков слишком сложно.

5. Но может ли так много потоков в состоянии ожидания из-за создания новых объектов службы исполнителя привести к увеличению потребления встроенной памяти отдельно от кучи? Это то, что я наблюдаю и пытаюсь понять / проверить