Hi Ruslan,
There is only 1 SelectorManager thread per HttpClient instance.
If you see HttpClient-18-SelectorManager, it's because at
least 18 (or 19?) HttpClient instances have been created.
Prior to JDK 21, the SelectorManager thread needs two conditions
before it can exit:
1. All strong references to the related HttpClient instance
must have been released by the caller code
2. All HTTP operations must have completed
Note that if you use things like:
- BodyHandler.ofInputStream()
- BodyHandler.ofLines()
then failing to close the returned InputStream or Stream
will cause a leak that will prevent the SelectorManager
thread from exiting.
So you must make sure to always close these - even if
a non-200 status is received.
Starting from JDK 21, the HttpClient has been made AutoCloseable,
and offers methods such as close(), shutdown(), shutdownNow(),
and awaitTermination(), to make it possible to explicitely
close the client when you no longer needs it.
Also the documentation with regard to closing the HttpClient
or releasing its resources has been improved.
See
https://docs.oracle.com/en/java/javase/23/docs/api/java.net.http/java/net/http/HttpClient.html#streaming
for more details.
best regards,
-- daniel
On 23/01/2025 10:30, Ruslan Ibragimov wrote:
I encounter strange behavior of java.net.http.HttpClient in my
application running in amazoncorretto:17.0.13, after some time instead
of just one HttpClient-1-SelectorManager thread I'm starting observing
dozens of such threads.
I'm creating only one instance of HttpClient in whole application, and
confirming with logging that following snippet called only once:
val httpClient = HttpClient.newBuilder()
.version(HttpClient.Version./HTTP_2/)
.followRedirects(HttpClient.Redirect./NEVER/)
.connectTimeout(Duration.ofSeconds(60))
.build()
I also verified that no dependencies using HttpClient under the hood.
"HttpClient-18-SelectorManager" #20791 daemon prio=5 os_prio=0 cpu=31.77ms
elapsed=3679.62s allocated=178K defined_classes=0 tid=0x00007f212c5dfce0 nid=0x541f
runnable [0x00007f21993ee000]
java.lang.Thread.State: RUNNABLE
at sun.nio.ch.EPoll.wait(java.base@17.0.13/Native Method)
at
sun.nio.ch.EPollSelectorImpl.doSelect(java.base@17.0.13/EPollSelectorImpl.java:118)
at
sun.nio.ch.SelectorImpl.lockAndDoSelect(java.base@17.0.13/SelectorImpl.java:129)
- locked <0x000000008c488e38> (a sun.nio.ch.Util$2)
- locked <0x000000008c488de8> (a sun.nio.ch.EPollSelectorImpl)
at
sun.nio.ch.SelectorImpl.select(java.base@17.0.13/SelectorImpl.java:141)
at
jdk.internal.net.http.HttpClientImpl$SelectorManager.run(java.net.http@17.0.13/HttpClientImpl.java:894)
Locked ownable synchronizers:
- None
So now I'm in situation when I see a lot (a few times they grow to 150+
threads) of such threads created over time and stay alive. I believe
it's also hurting application performance which lead even to timeouts on
http stack (but this is unconfirmed).
Any hint to understand why this might happen and how to debug root cause
would be appreciated.
--
Best regards,
Ruslan Ibragimov
https://heapy.io/ <https://heapy.io/>