考虑以下代码: package com.sarvagya;import java.util.Arrays;import java.util.List;import java.util.concurrent.ExecutionException;import java.util.concurrent.ForkJoinPool;import java.util.stream.Collectors;public class Streamer { priv
package com.sarvagya; import java.util.Arrays; import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.ForkJoinPool; import java.util.stream.Collectors; public class Streamer { private static final int LOOP_COUNT = 2000; public static void main(String[] args){ try{ for(int i = 0; i < LOOP_COUNT; ++i){ poolRunner(); System.out.println("done loop " + i); try{ Thread.sleep(50L); } catch (Exception e){ System.out.println(e); } } } catch (ExecutionException | InterruptedException e){ System.out.println(e); } // Add a delay outside the loop to make sure all daemon threads are cleared before main exits. try{ Thread.sleep(10 * 60 * 1000L); } catch (Exception e){ System.out.println(e); } } /** * poolRunner method. * Assume I don't have any control over this method e.g. done by some library. * @throws InterruptedException * @throws ExecutionException */ private static void poolRunner() throws InterruptedException, ExecutionException { ForkJoinPool pool = new ForkJoinPool(); pool.submit(() ->{ List<Integer> numbers = Arrays.asList(1,2,3,4,5,6,7,8,9,10, 11,12,14,15,16); List<Integer> collect = numbers.stream() .parallel() .filter(xx -> xx > 5) .collect(Collectors.toList()); System.out.println(collect); }).get(); } }
在上面的代码中,poolRunner方法正在创建一个ForkJoinPool并向其提交一些任务.当使用Java 8并将LOOP_COUNT保持为2000时,我们可以看到创建的最大线程大约为3600,如下所示
图:分析
图:线程信息.
经过一段时间后,所有这些线程都会下降到接近10.但是,在OpenJDK 11中保持相同的LOOP_COUNT会产生以下错误:
[28.822s][warning][os,thread] Failed to start thread - pthread_create failed (EAGAIN) for attributes: stacksize: 1024k, guardsize: 4k, detached. [28.822s][warning][os,thread] Failed to start thread - pthread_create failed (EAGAIN) for attributes: stacksize: 1024k, guardsize: 4k, detached. [28.822s][warning][os,thread] Failed to start thread - pthread_create failed (EAGAIN) for attributes: stacksize: 1024k, guardsize: 4k, detached. Exception in thread "ForkJoinPool-509-worker-5" java.lang.OutOfMemoryError: unable to create native thread: possibly out of memory or process/resource limits reached at java.base/java.lang.Thread.start0(Native Method) at java.base/java.lang.Thread.start(Thread.java:803) at java.base/java.util.concurrent.ForkJoinPool.createWorker(ForkJoinPool.java:1329) at java.base/java.util.concurrent.ForkJoinPool.tryAddWorker(ForkJoinPool.java:1352) at java.base/java.util.concurrent.ForkJoinPool.signalWork(ForkJoinPool.java:1476) at java.base/java.util.concurrent.ForkJoinPool.deregisterWorker(ForkJoinPool.java:1458) at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:187)
它很快达到最大线程限制.将LOOP_COUNT保持为500,工作正常,但是,这些线程非常缓慢地清除并达到大约500个线程的平台.见下图:
图:OpenJDK 11中的线程信息
图:OpenJDK中的分析11
线程在JDK 8中是PARKED,但在JDK 11中是WAIT.在Java 11中也应该减少守护程序线程的数量,但是它很慢并且不能按预期工作.而且,假设我无法控制poolRunner方法.考虑这种方法是由一些外部库提供的.
这是OpenJDK 11的问题,还是我在代码中做错了什么.谢谢.
你做错了.In above code, I am creating a
ForkJoinPool
and submitting some tasks to it.
实际上,你正在创建2000个ForkJoinPool实例……
您应该创建一个具有适合当前任务的并行度(即线程数)的ForkJoinPool,而不是这样做.
创建大量(即数千个)线程是一个非常糟糕的主意.即使您可以在不触发OOME的情况下执行此操作,您将消耗大量的堆栈和堆内存,并在调度程序和垃圾收集器上施加大量负载……没有任何实际好处.