Memory usage of 6 popular REST server frameworks compared

The Introduction

Picture an infrastructure with loosely coupled micro services, in a docker swarm environment. The services have REST end points and we use Spring Boot to realise these services.

Looking at the memory usage of these services however, being around 400MB each, they hardly qualify as ‘micro’ services.

This led to an investigation into the memory usage of Spring Boot. And a search for possible alternatives for Spring Boot.

The memory usage of spring is described by Dave Syer here, and here for Spring Boot in docker. A comparison of the various REST frameworks can be found here, but most comparisons don’t specifically look at memory usage.

So I decided to take 6 popular java frameworks with a REST feature, set up a simple “Hello World”  endpoint on them and measure the memory usage with a small load on the endpoint.

That way the memory usage of the framework itself can easily be measured.

I picked Spring Boot (obviously), DropWizard, Play, Vertx, Spark (Java) and Jersey (JAX-RS reference implementation) running on Tomcat 8.

The Setup

All the apps have a minimum setup, following the guidelines of the framework. They all have a simple get endpoint returning a string. For example for SpringBoot:

@RestController
public class HelloController {

    @GetMapping(value = "hello")
    public String hello() {
        return "hello-world";
    }
}

and for Spark :
public static void main(String[] args) throws UnknownHostException {
    get("/hello", (req, res) -> "Hello World");
}

which is probably the shortest java definition of the service of all frameworks.

Then a runnable jar is made which is done with maven-shade plugin for Spark, Vertex and Dropwizard, with the spring-boot maven plugin for Spring Boot.

Then the docker file to make the docker image:

FROM openjdk:8-jdk
COPY  ./target/minimal-boot-1.jar /usr/app/boot.jar
WORKDIR /usr/app
HEALTHCHECK --interval=5s  CMD curl -f http://localhost:8080/hello || exit 1
CMD java -XX:NativeMemoryTracking=summary -jar boot.jar

Here we use the docker health check with curl to check the service running correctly and to give a steady load, calling the service every 5 seconds.

The NativeMemoryTracking option is added to inspect the memory usage of this container in more detail (see results).

For Play an existing docker image on docker hub is used and extended, because of the hassle with nbt to make a runnable jar.

For Tomcat an existing docker image is used and extended adding curl for the health check and the generated war of the application:

FROM tomcat:9.0-jre8-alpine
RUN apk add --update curl && rm -rf /var/cache/apk/*
COPY target/minimal-jersey-1.war /usr/local/tomcat/webapps/jersey.war
HEALTHCHECK --interval=5s  CMD curl -f http://localhost:8080/jersey/hello/ || exit 1
CMD ["catalina.sh", "run"]

Then a compose file is used to start it all up :
version: '3.1'
services:
  springboot:
    image: boot
    environment:
      - "_JAVA_OPTIONS=${JAVA_OPTIONS}"
  play:
    image: play
    environment:
      - "_JAVA_OPTIONS=${JAVA_OPTIONS}"
  dropwizard:
    image: drop
    environment:
      - "_JAVA_OPTIONS=${JAVA_OPTIONS}"
  vertx:
    image: vertx
    environment:
      - "_JAVA_OPTIONS=${JAVA_OPTIONS}"
  spark: 
    image: spark 
    environment:
      - "_JAVA_OPTIONS=${JAVA_OPTIONS}"
  jersey:
    image: jersey
    environment:
      - "_JAVA_OPTIONS=${JAVA_OPTIONS}"

Here the JAVA_OPTIONS parameter is used to give all apps the same heap space properties,e.g.:
export JAVA_OPTIONS="-Xmx500m -Xms50m"
docker-compose up -d

The Results

For memory usage insights look at docker stats. When running the applications without heap constraints on a 2GB docker environment you’ll find something like this after a few minutes:

NAME                CPU %               MEM USAGE / LIMIT     MEM %               NET I/O             BLOCK I/O           PIDS

rest_springboot_1   2.83%               248.3MiB / 1.952GiB   12.42%              1.53kB / 0B         21.3MB / 0B         33
rest_spark_1        2.67%               31.96MiB / 1.952GiB   1.60%               1.71kB / 0B         12.5MB / 0B         27
rest_vertx_1        2.70%               47.8MiB / 1.952GiB    2.39%               1.78kB / 0B         22.4MB / 0B         21
rest_jersey_1       0.14%               129.7MiB / 1.952GiB   6.49%               1.71kB / 0B         25.3MB / 0B         49
rest_play_1         4.16%               176.5MiB / 1.952GiB   8.83%               1.46kB / 0B         29.6MB / 32.8kB     33
rest_dropwizard_1   3.19%               139.2MiB / 1.952GiB   6.97%               1.78kB / 0B         16.7MB / 0B         35

You discover immediatly that Spring has the most memory usage, while Spark uses the least.

To analyse the effect of the heap size we do the same measurement with fixed heap size (-Xms and Xmx = 50M), and get the following results :

NAME                CPU %               MEM USAGE / LIMIT     MEM %               NET I/O             BLOCK I/O           PIDS

rest_springboot_1   0.16%               151MiB / 1.952GiB     7.56%               1.65kB / 0B         0B / 0B             33
rest_spark_1        0.15%               35.68MiB / 1.952GiB   1.78%               1.65kB / 0B         0B / 0B             27
rest_vertx_1        0.10%               50.94MiB / 1.952GiB   2.55%               1.73kB / 0B         0B / 0B             20
rest_jersey_1       0.17%               110.8MiB / 1.952GiB   5.55%               1.73kB / 0B         0B / 0B             49
rest_play_1         1.10%               88.76MiB / 1.952GiB   4.44%               1.65kB / 0B         0B / 32.8kB         33
rest_dropwizard_1   0.17%               127.3MiB / 1.952GiB   6.37%               1.82kB / 0B         12.3kB / 0B         34

Now we see Spring Boot still using a large amount of memory, far more than the heap space usage. While Spark and Vertx use very little of the heap and have a very small memory footprint.

To analyse this large memory usage of Spring we use the native memory tracking java tool:

docker exec rest_springboot_1 jcmd 6 VM.native_memory summary

giving:

Total: reserved=1443704KB, committed=158176KB

-                 Java Heap (reserved=51200KB, committed=51200KB)
                            (mmap: reserved=51200KB, committed=51200KB) 
-                     Class (reserved=1083313KB, committed=36657KB)
                            (classes #5470)
                            (malloc=6065KB #6169) 
                            (mmap: reserved=1077248KB, committed=30592KB) 
-                    Thread (reserved=33037KB, committed=33037KB)
                            (thread #33)
                            (stack: reserved=32896KB, committed=32896KB)
                            (malloc=103KB #166) 
                            (arena=38KB #64)
-                      Code (reserved=251589KB, committed=12717KB)
                            (malloc=1989KB #3475) 
                            (mmap: reserved=249600KB, committed=10728KB)  
-                        GC (reserved=7648KB, committed=7648KB)
                            (malloc=5772KB #159) 
                            (mmap: reserved=1876KB, committed=1876KB) 
-                  Compiler (reserved=145KB, committed=145KB)
                            (malloc=14KB #152) 
                            (arena=131KB #3)
-                  Internal (reserved=6461KB, committed=6461KB)
                            (malloc=6429KB #7119) 
                            (mmap: reserved=32KB, committed=32KB) 
-                    Symbol (reserved=8969KB, committed=8969KB)
                            (malloc=6212KB #56128) 
                            (arena=2757KB #1)
-    Native Memory Tracking (reserved=1156KB, committed=1156KB)
                            (malloc=6KB #74) 
                            (tracking overhead=1150KB)
-               Arena Chunk (reserved=186KB, committed=186KB)
                          (malloc=186KB)

Looking at the committed memory we see the spring has loaded a lot of classes (5470, using 36MB) and lots of space for threads (33MB).

Also the code section (generated code by cglib etc.) is contributing with 12MB. This the effect of the large library of the spring components used by springboot.

We also analysed the effect of a greater payload. We varied the payload from 1 per second to the maximum payload the CPU usage of the component would allow in our setup. This was mostly around 1000 request per second. For each measurement we waited for the memory usage to be constant, which sometimes decreased due to garbage collection. In the result we see that Vertx and Spark stayed constant a a low memory usage. Also is seen that Play’s memory usage is increasing significant due to the greater payload.

In Conclusion

If a real micro service with a small memory footprint is what you are looking for Spark and Vertx are the best candidates of the 6. Spring Boot proved to be the worst.

15 thoughts on "Memory usage of 6 popular REST server frameworks compared"

  1. Marc Enschede says:

    When I saw the company name (Craftsman) I hoped you would pay less attention to tools and frameworks and pay more attention to (programming) techniques and design principles like TDD, BDD, DRY, SOLID, DDD, etc.

  2. Cees says:

    Nice experiment! I’m curious to learn how memory usage increases when load increases did you test that? These frameworks probably scale differently.

    1. Gerlo Hesselink says:

      I will try that shortly. Will post the results here.

    2. Gerlo Hesselink says:

      See the blog !

  3. Stephan Oudmaijer says:

    Also have a look at this article for more details on Spring Boot memory usage: https://spring.io/blog/2015/12/10/spring-boot-memory-performance

  4. Earl says:

    Hey, just leaving constructive feedback, next time you use a graph similar to the one above its probably worth sacrificing some design for usability and use different colors instead of 6 different shades of grey.

    1. Craftsmen says:

      Thanks! We will take your feedback in mind.

  5. Kees van den Berg says:

    Nice blog, we are also using spring boot for out services, and find the memory usage large even when the services are basic.

  6. Pawel says:

    Please include http4s

  7. pablo says:

    Move on to golang! guys!.

    microservices of 10mb and 20 millicores of CPU!.

  8. Great article. We have been experiencing exactly the memory problem in our Spring boot applications. Maybe Spark is the way to go in our case. Greetings.

  9. Ladislav Jech says:

    Next time you do this benchmark please include light4j framework (its more like platform for event driven system with independent modules to be used as standalone)

    https://github.com/networknt/microservices-framework-benchmark

  10. lbt05 says:

    would you please change the color diagram above conclusion section ?
    it’s so hard to tell

  11. Reza says:

    You may want to include micronaut for the comparisson…

  12. Rudolf de Grijs says:

    Nice article, thank you.

    We live in a time were where we are charged per byte. That’s how these cloud providers operate.

    Of course it is important to take good design practices into consideration, but as craftsmen we also have to take the resource usage into account. These costs keep on lingering indefinitely.

Leave a Reply

Your email address will not be published. Required fields are marked *