La función parallelStream() de Java 17 genera problemas críticos de rendimiento en el código que funciona correctamente en Java 16. ¿Por qué sucede esto?
Trabajo en una aplicación web Jetty que se ejecutaba en Java 16. Intenté actualizarla a Java 17, pero hubo problemas críticos de rendimiento causados enteramente por una llamada a parallelStream()
.
Los únicos cambios son la actualización de la versión de Java de 16 a 17, --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED
y la actualización del tiempo de ejecución de openjdk:16.0.1-jdk-oraclelinux8
a openjdk:17.0.1-jdk-oraclelinux8
.
Logramos obtener un volcado de hilos y contiene muchos de estos:
"qtp1368594774-200" #200 prio=5 os_prio=0 cpu=475.94ms elapsed=7189.65s tid=0x00007fd49c50cc10 nid=0xd1 waiting on condition [0x00007fd48fef7000]
Estado del hilo java.lang.Thread: ESPERANDO (estacionamiento)
en jdk.internal.misc.Unsafe.park(java.base@17.0.1/Native Method)
- estacionándose para esperar a <0x00000007b73439a8> (un java.util.stream.ReduceOps$ReduceTask)
en java.util.concurrent.locks.LockSupport.park(java.base@17.0.1/LockSupport.java:341)
en java.util.concurrent.ForkJoinTask.awaitDone(java.base@17.0.1/ForkJoinTask.java:468)
en java.util.concurrent.ForkJoinTask.invoke(java.base@17.0.1/ForkJoinTask.java:687)
en java.util.stream.ReduceOps$ReduceOp.evaluateParallel(java.base@17.0.1/ReduceOps.java:927)
en java.util.stream.AbstractPipeline.evaluate(java.base@17.0.1/AbstractPipeline.java:233)
en java.util.stream.ReferencePipeline.collect(java.base@17.0.1/ReferencePipeline.java:682)
en com.stackoverflowexample.aMethodThatDoesBlockingIOUsingParallelStream()
El código que está causando el problema es algo como:
list.parallelStream()
.map(this::callRestServiceToGetSomeData)
.collect(Collectors.toUnmodifiableList());
Esta imagen muestra el uso de hilos antes de la actualización de jdk16 (izquierda), la actualización a jdk17 (el pico enorme en el medio), y luego eliminando la llamada a parallelStream()
aún en jdk17 (derecha):
¿Qué cambio en Java 17 (openjdk-17.0.1linux-x64bin.tar.gz) ha causado esto?
davy.ai
El problema se debe a un cambio en la forma en que se maneja
ForkJoinTask
en Java 17. Específicamente, se agregó una nueva función llamada “FJ Tasks interruptible in park” en Java 17, que permite interrumpirForkJoinTask
cuando están bloqueados en una llamada apark()
.En el código proporcionado, la llamada
parallelStream()
está haciendo que los hilos se bloqueen en una llamada apark()
, esperando a que elForkJoinTask
se complete. Sin embargo, debido a que Java 17 introdujo la capacidad de interrumpirForkJoinTask
en park, los hilos se interrumpen y se reprograman, lo que provoca un gran aumento en el uso de hilos.Para solucionar este problema, una solución es cambiar de usar
parallelStream()
a usarstream()
en su lugar, lo cual evitará que los hilos se bloqueen y evitará el impacto en el rendimiento causado porForkJoinTask
interrumpible.Alternativamente, si es necesario utilizar
parallelStream()
, puede intentar aumentar el número de hilos enForkJoinPool
configurando la propiedad del sistemajava.util.concurrent.ForkJoinPool.common.parallelism
a un valor más alto, lo cual puede ayudar a aliviar el impacto en el rendimiento causado porForkJoinTask
interrumpible.