HTTP/2 Client has a very poor scalability. The source for that is over synchronization.
There are two points for that:
- java.net.Queue class - synchronization + callbacks leads to that only one thread may performs progress, besides the Queue has generic implementation for all usage.
At the same moment more specialized queues implementation could be more effective in particular cases.
- Http2Connection.sendFrame implementation do global (for the whole connection) synchronization on "sendlock".
That causes a large nested sequence of locks which a large critical sections (all synchronized actions are marked with *):
* Http2Connection.sendFrame lock, here are doing:
- headers encoding
- streams number assignments
- frames encoding (to byte buffers)
* enqueuing ByteBufers to output queue:
- callback invocation:
* lock on Connection global write lock:
* write to SocketChannel and subsequent internal SocketChannel locks.
That all leads to the fact: in multythreaded HttpClient execution each user thread (thread there requests to HttpClient performed) utilization never reach 10%.
SelectorManager thread reaches 30% CPU utilization.
Suggested fix move possible actions out of synchronized section, other actions where ordering is important enclosed into smaller critical sections with more effective synchronization primitives.
As result performance improved up to 3x times at some scenarios and scalability was significantly improved (see charts).
Webrev could be found here:
http://cr.openjdk.java.net/~skuksenko/jep110/8164001/