Iocp
class that performs all the asynchronous I/O tasks in NIO.2 file channels is, by default, backed by a so called "cached" thread pool. That's a thread pool that creates new threads as needed, but will reuse previously constructed threads *when* they are available. Look at the code of the ThreadPool
class held by the Iocp
.The thread pool in the default channel group is constructed as
ThreadPoolExecutor
with a maximum thread count of Integer.MAX_VALUE and a keep-alive-time of Long.MAX_VALUE. The threads are created as daemon threads by the thread factory. A synchronous hand-over queue is used to trigger thread creation if all threads are busy. There are several issues with this configuration: 1. If you perform write operations on asynchronous channels in a burst you will create thousands of worker threads which likely results in an
OutOfMemoryError: unable to create new native thread
. 2. When the JVM exits, then all deamon threads are abandoned - finally blocks are not executed, stacks are not unwound.
In my other blog I have explained why unbounded thread pools can 'cause trouble. Therefore, if you use asynchronous file channels, it may be an option to use custom thread pools instead of the default thread pool. The following snippet shows an example custom setting.
The javadoc of
AsynchronousFileChannel
states that the custom executor should "minimally [...] support an unbounded work queue and should not run tasks on the caller thread of the execute method." That's a risky statement, it is only reasonable if resources aren't an issue, which is rarely the case. It may make sense to use bounded thread pools for asynchronous file channels. You cannot get a too-many-threads issue, also you cannot flood your heap with work queue tasks. In the example above you have five threads that execute asynchonous I/O tasks and the work queue has a capacity of 2500 tasks. If the capacity limit is exceeded the rejected-execution-handler implements the CallerRunsPolicy
where the client has to execute the write task synchronously. This can (dramatically) slow down the system performance because the workload is "pushed back" to the client and executed synchronously. However, it can also save you from much more severe issues where the result is unpredictable. It's a good practice to work with bounded thread pools and to keep the thread pool sizes configurable, so that you can adjust them at runtime. Again, to learn more about robust thread pool settings see my other blog entry.Thread pools with synchronous hand-over queues and unbound maximum thread pool sizes can aggressively create new threads and thus can seriously harm system stability by consuming (pc registers and java stacks) runtime memory of the JVM. The 'longer' (elapsed time) the asynchronous task, the more likely you'll run into this issue.
Thread pools with unbounded work queues and fixed thread pool sizes can aggressively create new tasks and objects and thus can seriously harm system stability by consuming heap memory and CPU through excessive garbage collection activity. The larger (in size) and longer (in elapsed time) the asynchronous task the more likely you'll run into this issue.
That's all in terms of applying custom thread pools to asynchronous file channels. My next blog in this series will explain how to close asynchronous channels safely without loosing data.
The NIO.2 file channels series:
- Introduction
- Applying custom thread pools
- Closing file channels without loosing data
- I/O operations are not atomic
Hi Niklas
ReplyDeleteYour blog is too good. Could you please post a blog on how to gain knowledge on architectural side of things. What i mean is could you please write on how to move to the next level from programming to design and architecture. Any Thanks in advance.
Hi there! I was thinking about it for a while, an architecture series. It's interesting that you call it the 'next level'. I started as a developer and then entered 'the next level' and worked as an architect. I didn't do much programming for years. And then I've realized that my judgements/evaluations got worse. Since that time I'm spending 50% of my time with programming, since there is no 'technical' architect w/o superior up-to-date development (API, framework etc.) expertise. Therefore I wouldn't call it 'the next level' anymore, it's about sharing knowledge, community and taking responsibility in a team of technical experts. May be what I just said is a good start for an architecture series. Thx for the very nice comment!! Cheers, Niklas
ReplyDeleteHmm to the point. Hope to see your architecture series soon :)
ReplyDeleteHi Niklas. Thanks for the articles....
ReplyDeleteWhat is the effect of the calling thread issuing yield on itself when the CallerRunsPolicy is active?
I would think that the client issues asynchronous I/O operations to the underlying operating system and all pool threads handle the I/O events in the I/O completion port. There is no queueing of WriteTasks to the work queue in use.
Delete