• Stars
    star
    141
  • Rank 259,971 (Top 6 %)
  • Language
    Java
  • License
    MIT License
  • Created 11 months ago
  • Updated 9 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Benchmarks for JDK HTTP Server running on Java 21 with Virtual Threads

Overview

The code and experiments shown in this repository are intended to demonstrate the capabilities of a simple, minimal Java web server application built atop the following JDK primitives:

Experiments were conducted in AWS cloud on modest c5.2xlarge EC2 instances with 8 vCPU and 16 GB RAM.


The same experiments were conducted with GraalVM:

  • 105,000+ req/sec hello-world server (0.8 ms per req)
  • 3 millisecond startup
  • 13 millisecond time-to-response
  • 18 MB native image

Environment

  • c5.2xlarge EC2 instances
  • Amazon Linux AMI 2.0.20231219 x86_64 ECS HVM GP2 ami-0d63309a12899f79d
  • OpenJDK 21.0.1
  • wrk HTTP benchmarking tool

Launch instance:

aws ec2 run-instances --image-id ami-0d63309a12899f79d ...

Download OpenJDK:

curl -s "https://download.java.net/java/GA/jdk21.0.1/415e3f918a1f4062a0074a2794853d0d/12/GPL/openjdk-21.0.1_linux-x64_bin.tar.gz" --output openjdk-21.0.1_linux-x64_bin.tar.gz
tar xvf openjdk-21.0.1_linux-x64_bin.tar.gz

Download and build wrk

sudo yum install git
sudo yum install "Development Tools"
git clone https://github.com/wg/wrk.git
cd wrk
make

Compile and Build

Compile source code with a javac command in the project directory.

javac -d mods/ src/httpsrv/*.java src/module-info.java

Create a custom, modular run-time image with a jlink command. JAVA_HOME refers to the OpenJDK installation directory.

jlink --module-path $JAVA_HOME/jmods:mods --add-modules httpsrv --output httpsrvimg

The contents of the resulting httpsrvimg are 57 MB.

$ du -h httpsrvimg/
364K	httpsrvimg/lib/security
25M	httpsrvimg/lib/server
56M	httpsrvimg/lib
4.0K	httpsrvimg/conf/sdp
12K	httpsrvimg/conf/security/policy/limited
8.0K	httpsrvimg/conf/security/policy/unlimited
24K	httpsrvimg/conf/security/policy
92K	httpsrvimg/conf/security
104K	httpsrvimg/conf
4.0K	httpsrvimg/include/linux
196K	httpsrvimg/include
48K	httpsrvimg/bin
116K	httpsrvimg/legal/java.base
0	httpsrvimg/legal/java.net.http
0	httpsrvimg/legal/jdk.httpserver
116K	httpsrvimg/legal
57M	httpsrvimg/

Start-up Time

The Hello Server writes a "ready" message to standard output after launch. This serves as a timing signal that indicates the conclusion of the JVM and application start-up process.

During each timing cycle, the profile.sh shell script measures

  1. System time prior to start
  2. System time after "ready" message received in stdin
  3. System time after GET / 200 OK response received

The difference between [1] and [2] is the start-up time and the difference between [2] and [3] is the time-to-response.

The profile.sh shell script measures these differences across 10 launches and prints the averages.

Start-up is 0.101 seconds on average.

Time-to-response is 0.179 seconds on average.

$ ./profile.sh 
0.116000, 0.191000, 200
0.097000, 0.185000, 200
0.096000, 0.175000, 200
0.108000, 0.189000, 200
0.097000, 0.170000, 200
0.098000, 0.178000, 200
0.097000, 0.170000, 200
0.108000, 0.183000, 200
0.102000, 0.178000, 200
0.098000, 0.172000, 200
---
0.101700, 0.179100

Hello Server

Hello is a simple application that starts a JDK HTTP server.

It responds to every HTTP request with a "hello world" plain-text response.

It uses a virtual-thread-per-task executor.

./httpsrvimg/bin/java -Dsun.net.httpserver.nodelay=true -m httpsrv/httpsrv.Hello

In the benchmark setting, two EC2 instances were used. One instance ran the Hello server and the other instance ran wrk.

+---------+                +---------+
|         |                |         |
|   wrk   +--------------->|  Hello  |
|         |                |         |
+---------+                +---------+

The following parameters were used for the wrk benchmark below:

  • 100 concurrent connections (-c)
  • 60 second duration (-d)
  • 8 threads (-t)

The resulting request rate for the execution below was 117,448 requests per second with an average latency of 844 microseconds.

$ ./wrk --latency -d 60s -c 100 -t 8 http://10.39.197.177:8080/
Running 1m test @ http://10.39.197.177:8080/
  8 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   844.44us    1.64ms 203.40ms   99.91%
    Req/Sec    14.78k     6.84k   24.85k    51.03%
  Latency Distribution
     50%  627.00us
     75%    0.94ms
     90%    1.71ms
     99%    2.04ms
  7058584 requests in 1.00m, 585.65MB read
Requests/sec: 117448.07
Transfer/sec:      9.74MB

Gateway Server

Gateway is a simple application that starts a JDK HTTP server.

It forwards every HTTP request to a separately deployed Hello server via the JDK HTTP client. Similarly, the Hello server response is forwarded back to the Gateway client.

It uses a virtual-thread-per-task executor.

./httpsrvimg/bin/java -Dsun.net.httpserver.nodelay=true -m httpsrv/httpsrv.Gateway http://10.39.197.199:8080/ 

In the benchmark setting, three EC2 instances were used. One instance ran the Gateway server, another ran the Hello server, and the last instance ran wrk.

+---------+                +-----------+                 +---------+
|         |                |           |                 |         |
|   wrk   +--------------->|  Gateway  +---------------->|  Hello  |
|         |                |           |                 |         |
+---------+                +-----------+                 +---------+
  • 100 concurrent connections (-c)
  • 60 second duration (-d)
  • 8 threads (-t)

The resulting request rate for the execution below was 53,485 requests per second with an average latency of 1.82 milliseconds.

$ ./wrk --latency -d 60s -c 100 -t 8 http://10.39.197.177:8080/
Running 1m test @ http://10.39.197.177:8080/
  8 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     1.82ms    1.99ms 203.11ms   99.03%
    Req/Sec     6.72k   397.07     8.32k    81.67%
  Latency Distribution
     50%    1.68ms
     75%    2.04ms
     90%    2.50ms
     99%    3.80ms
  3209334 requests in 1.00m, 266.28MB read
Requests/sec:  53485.25
Transfer/sec:      4.44MB

Caveats

Despite being in the standard library and despite the promising throughput numbers shown here, the JDK HTTP server is not optimized for performance, and it doesn't represent a fast Java HTTP server.

Furthermore, its feature set is limited. For example, it doesn't support HTTP/2.

This isn't the case elsewhere. For example, the Go std lib web server, which has a very similar lightweight feel, is widely used and does prioritize performance and standards compliance.

Evaluate the benchmarks shown here in that context.

The JDK HTTP server is adequate for many workloads and usecases, but if you're looking for a high-performance Java HTTP Server, consider the following alternatives:

GraalVM

GraalVM ahead-of-time native-image technology removes the overhead of class loading, just-in-time compilation, and other JVM responsibilities.

The command below creates an executable binary called httpsrv:

native-image --module httpsrv/httpsrv.Hello --class-path mods --module-path mods -o hellosrv -Dsun.net.httpserver.nodelay=true

GraalVM CE 21.0.2 was used:

$ java --version
openjdk 21.0.2 2024-01-16
OpenJDK Runtime Environment GraalVM CE 21.0.2+13.1 (build 21.0.2+13-jvmci-23.1-b30)
OpenJDK 64-Bit Server VM GraalVM CE 21.0.2+13.1 (build 21.0.2+13-jvmci-23.1-b30, mixed mode, sharing)

The resulting binary is just 18.7 MB:

$ ls -l | grep hellosrv
-rwxrwxr-x 1 ec2-user ec2-user 18727456 Feb 26 18:09 hellosrv

And the start-up times go down significantly:

  • 3 millisecond startup
  • 13 millisecond time-to-response
$ ./profile.sh 2> /dev/null 
0.003000, 0.014000, 200
0.004000, 0.014000, 200
0.003000, 0.013000, 200
0.004000, 0.014000, 200
0.003000, 0.013000, 200
0.003000, 0.013000, 200
0.003000, 0.013000, 200
0.003000, 0.013000, 200
0.004000, 0.014000, 200
0.004000, 0.013000, 200
---
0.003400, 0.013400

The profile.sh script changed slightly to use the hellosrv executable:

diff --git a/profile.sh b/profile.sh
index a69330d..2ee9515 100755
--- a/profile.sh
+++ b/profile.sh
@@ -7,7 +7,7 @@ getCurrentTime() {
 }
 
 runHttpServer() {
-    ./httpsrvimg/bin/java -Dsun.net.httpserver.nodelay=true -m httpsrv/httpsrv.Hello
+    ./hellosrv
 }
 
 profileHttpServer() {
@@ -17,7 +17,7 @@ profileHttpServer() {
     local curlEndTime=$(getCurrentTime)
 
     echo "$1 $javaEndTime $curlEndTime $statusCode"
-    pkill java
+    pkill hellosrv
 }
 
 makeCurlRequest() {

Benchmark with GraalVM native image:

$ ./wrk --latency -d 60s -c 100 -t 8 http://10.39.197.177:8080/
Running 1m test @ http://10.39.197.177:8080/
  8 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     0.97ms    2.21ms 209.18ms   98.85%
    Req/Sec    13.30k     0.87k   20.41k    73.59%
  Latency Distribution
     50%    0.85ms
     75%    1.04ms
     90%    1.25ms
     99%    3.38ms
  6360869 requests in 1.00m, 527.76MB read
Requests/sec: 105838.03
Transfer/sec:      8.78MB

More Repositories

1

microhttp

Fast, scalable, self-contained, single-threaded Java web server
Java
527
star
2

project-loom-c5m

Experiment to achieve 5 million persistent connections with Project Loom virtual threads
Java
357
star
3

game-of-life-csp

Game of Life implemented in Java using virtual threads and communicating sequential processes
Java
134
star
4

project-loom-comparison

A comparison of different methods for achieving scalable concurrency in Java
Java
65
star
5

minesweeper-csp

Minesweeper implemented in Java using virtual threads and communicating sequential processes
Java
50
star
6

minesweeper-go-csp

Minesweeper implemented in Go using Goroutines and communicating sequential processes
Go
25
star
7

TrippinOnTubs

A cross-platform endless-runner game implemented with C++ and built atop SDL2.
C++
18
star
8

project-loom-experiment

Echo client-server components to evaluate Project Loom virtual threads.
Java
17
star
9

earth-moon-model

A tabletop digital art project that models the Earth and Moon
Python
4
star
10

kotlin-echo-client

Echo client using Kotlin with Ktor networking library
Kotlin
2
star
11

minesweeper_ftxui

A Minesweeper adaption that consists of a timed game made up of rounds of increasing difficulty
C++
2
star
12

microhttp-client

Simple, synchronous, virtual-thread-ready HTTP client library
Java
2
star
13

noaatides

Python
1
star
14

buoy-twitter-bot

Buoy Twitter Bot
Python
1
star
15

java-nio-demo

Demonstration of idiomatic, single-threaded, event-driven Java NIO networking
Java
1
star
16

tidemeter

Python
1
star
17

synergy-one-tracker

LED matrix digital art piece displaying UMBS metrics
C++
1
star
18

wave-tracker-led-matrix

Wave Tracker LED Matrix Raspberry Pi Project
Python
1
star
19

clojure-minesweeper

The classic Microsoft Minecraft game implemented in Clojure.
Clojure
1
star