Book Review: Supercharge Your Applications with GraalVM

Carlos Santana
11 min readAug 16, 2021

--

This weekend I finished reading the book Supercharge Your Applications with GraalVM authored by. A B Vijay Kumar

Supercharge Your Applications with GraalVM book cover

I went over my notes and highlights on the book and started writing thoughts for each chapter; I came out with this. I don’t know if this is a review, a summary, or just my own personal notes on the book, but I wanted to share it, not expecting anyone to read them. This is what blogging is, right?

TLDR; You are going to enjoy the book because you’re going to discover that GraalVM is not just about compiling java apps into small native binaries.

The book provides a great journey starting with the evolution of java virtual machines (JVM) and ending with serverless apps, with the bonus of having hands-on examples with a companion GitHub repository. You don’t have to have years of experience in Java to read and understand this book; even if you are a programmer in another language, this book will benefit from understanding interpreters and compilers.

Chapter 1 covers how code is optimized at the different stages with the interpreter and compiler. Walks through how code is executed in the JVM and at the same time is optimized as it runs. I like the explanations on the roles of the C1 and C2 compilers, especially on code optimizations, but more interesting learning about Deoptimization, one of the critical parts of the JVM pipeline. When I started reading this chapter, I asked myself Why I’m reading an overview on the JVM? Only to realize how critical it was to start the book with this foundation.

Chapter 2 goes deep into the C2 just-in-time (JIT) compiler and introduces GraalVM JIT. To appreciate the GraalVM JIT compiler, it’s essential to understand how the C2 JIT compiler works since GraalVM would be replacing it, providing more benefits. The author does an excellent job going over the basics of the Compiler like code cache, compiler threshold, and on-stack replacement using basic code examples and running them, showing their effects on the terminal. I like that the author introduces the use of XX:+PrintCompilation of JITWatch, a tool to visualize the JIT. You might think these tools are only for compiler programmers, but they are instrumental in environments that performance is critical in large-scale solutions. In chapter 1, the C2 JIT attributes were just mentioned as bullets points. In chapter 2 it goes over the different optimizations performed by the JIT like inlining, dead code elimination, and loop unrolling. It goes deeper on escape analysis as the JIT compiler allocates variables in the “stack area” instead of the “heap area” or even converts them to PC registers. This is one of the most complex optimizations for the JIT and has a huge performance impact.

Deoptimization with non-entrant code and zombie code is also covered. After going into all the complexities that the current C2 JIT compiler needs to deal with, the downside of the C2 is implemented in C/C++ while fast. It is not type-safe without garbage collection. So the JDK decided to write an Interface (JVMCI) to allow a different implementation of the C2 JIT component in another language to allow for easier and innovations to be provided to the JVM. This is where GraalVM comes into play that modern implementation is GraalVM JIT written in java. And now GraalVM is an implementation of the JVMCI, which brings all the key features and optimizations required for modern runtimes. The focus of Graal is much more than just JIT compilation targeting the cloud-native requirements of polyglot, smaller footprint, faster build and run. Graal does not focus on JVM languages (Scala, Kotlin, etc.) but also dynamic languages (ruby, javascript, python) as well native (C/C++, R, llvm based).

Chapter 3 covers the GraalVM architecture; it provides an understanding of the different architectural components of GraalVM. While GraalVM is built on Java, it supports Java and enables Polyglot development (one runtime many languages) with JavaScript, Python, R, Ruby, C, and C++. It provides a framework Truffle with one of the chapters dedicated to it. GraalVM also provides AOT compilation to build native images with static linking. I was surprised to see such a rich set of architecture components, from LLVM toolchains to dynamic language interpreters and GraalWasm, a WASM implementation. In this chapter, you learn about the two editions community and enterprise and their differences. This chapter is vibrant in terms of concepts. It shows how the runtime can support many languages in a single process, how the compilation to native binary works, and how developers could use profile Guided Optimization (PGO) to have the best of both worlds to have a native binary built and optimized with previous data collected from running the program. I think PGO is the coolest thing I learned from the book. The chapter is rich because it also covers the security aspect of having different language programs running under one runtime and how different languages can exchange data between the different contexts. There are 2 pages worth of CICD towards the end, which I think illustrates that GraalVM doesn’t have special requirements that would impede integration into a traditional or modern CICD pipeline.

Chapter 4 covers setting up the environment like installing GraalVM, understanding the Graal JIT compiler, understanding Graal compiler optimizations, and debugging and monitoring applications. When you install GraalVM is a drop-in replacement for your JDK, it contains the same binary names like javac and java. The chapter introduces the user to the VisualVM tool provided by Graal that supports Graal graphs and other languages (JavaScript, Python, Ruby, and R). Using VisualVM, you can connect to a remote process, and this is the way I have used it in cloud-native environments like Kubernetes. It allows you to tap into the process and see things like CPU, memory management, threads, classes, etc. The chapter shows how to use the JDK Flight Recorder (JFR), a powerful feature of VisualVM. Another tool the author introduces is Idea Graph Visualizer, which analyzes how the Graal JIT optimizes the code being analyzed. I like that the author goes into detail on how to run and the options the Graal compiler is; this is very useful for DevOps folks as they are usually in charge of build pipelines and provide this as inputs to development teams. Compiler geeks, or if you are in college taking a compiler class, the author goes out his way to explain certain aspects that compiler deals with like Intermediate Representations (IRs) data structures and Static Single Assignments (SSA). I’m glad that the author introduces the tool Graph Visualizer as he shows the different phases of analysis in optimizations, and users can verify them in the tool. The chapter ends with introducing some debugging tools like the VSCode GraalVM Extension Pack, the GraalVM Dashboard, the profiling CLI and the code coverage CLI. All these tools are useful when working with GraalVM on your workstation.

Chapter 5 provides a hands-on tutorial that walks through how to build small native images and run these with profile-guided optimization. The Graal ahead-of-time (AOT) compiler lets us build the code and include it in a single binary. AOT's step is Points-to Analysis, which identifies only the classes and methods used and eliminates all the code never used or called. I learned how to deal with code that uses reflection; the solution is to use a meta-fie in JSON that contains the information about the code, not a direct reference to being included in the native image. In this chapter, you get a complete view of what it takes to build that small footprint image, but why it starts so fast. Another interesting step in the process is the Region Analysis and Heap Snapshot. Heap snapshotting gathers all the objects that are reachable at runtime. The final native image has the code section, but the data section where the heap image is written allows for a faster start. In this chapter, the author shows how to use the GraalVM Dashboard. This allows us to visualize how the code size is broken down, including shared libraries. The dashboard also allows us to see the heap size breakdown; this allows us to verify the large parts and where they come from. The chapter also explains PGO and how to use it; the idea is that once you create the native image, it can’t longer be optimized with the C2 compiler. PGO is a clever idea that allows us to run the code and capture a runtime profile; then, we can feed the profile when creating the native image one more time for production, building a second native image that is now optimized with the profile. This is the last chapter on java; the next chapter starts the section on Polyglot on how you can use GraalVM with other languages.

Chapter 6 is an introduction and overview of the framework Truffle, which will go deep with different languages in subsequent chapters. GraalVM provides benefits in terms of optimizing code for JVM runtimes such as Java, Groovy, Kotlin, and Scala and non-JVM languages such as JavaScript, Ruby, Python, R WebAssembly, and LLVM languages. A lot more languages are being implemented on Truffle; at this time, golang is not supported; there is an issue opened. The chapter does a great job explaining what Truffles provides is a framework, not a runtime; it utilizes the Graal compiler features to generate the high-performance code. The key here is that some languages don’t provide the optimizations that Graal compilers provide, so in practice, if you have server code that is critical to be performant, I would highly suggest running some performance tests of your code using Graal and compare to the solution you’re using today, depending on the opportunities for optimization you would be surprised how much better your code would run using a Graal. The chapter details how Truffle works on a guest language and how once having an Abstract Syntax Tree (AST) of your code. The code can be self-optimized and tree re-written. The AST interpreter can rewrite the AST dynamically based on the runtime profiling. This improves the performance significantly because the nodes can be updated dynamically. Truffle provides a Dynamic Object Model (DOM) that provides a framework to enable the interoperability of data and objects between different languages in the same runtime.

Chapter 7 covers how to run JavaScript programs with Node.js using GraalVM; the Truffle framework is used to run the JavaScript programs. GraalVM comes with a node and npm binary that allows being a drop-in replacement in your workstation. Graal will interpret the code with the binary provided by GraalVM. Still, the most important difference the code will be profiled and optimized, which is a big difference if you just run the code with the traditional nodejs runtime. The nodejs interpreted provided by GraalVM comes from the same source code as the upstream Node.js replacing the V8 JavaScript engine with the GraalVM JavaScript engine. The chapter starts with a simple web server application using the express framework and running it GraalVM just executing the binary node. Then the authors introduce the interoperability of the JVM, allowing Java code to call the JavaScript. For this, you need to load both the JavaScript from a string, but it also can be done from a more practical file. An example shows how to execute java code from JavaScript and explains how data types are handled between the two contexts. I like the fact that the authors cover multithreading with nodejs. If you have been hit with a performance bottleneck in your existing nodejs app, you can take a look at GraalVM support for multithreading; this will allow you to run your node.js code in a safe multithread environment while at the same time benefiting from the compiler optimizations as the code is being profiled, in case of native images you can use PGO as explains in the past chapter.

Chapter 8 introduces Expresso which is the ability to run Java as a guest language on Truffle. You might think is counter-intuitive to have an additional layer on top of Graal. Of the many advantages the author goes over in the chapter, a few of them is to have the ability to hot-swap methods/lambdas and access modifiers at debug time, a sandbox to run untrusted java code (i.e., cloud/serverless multi-tenancy), AOT compilation useful for native-images to allow reflection/JNI out of the box without the need of the custom builds/process, and run different java versions (i.e., java 8, java 11) in a single runtime. The tooling to work with Expresso is similar to the one introduced in the previous chapter with node.js. This java code running Expresso can interoperate with other languages such as javascript and python. In this chapter, the author shows how to run python in truffle and be called from java. The use case is very valid for applications that need to leverage solutions written in python or R for data science. It is most common and easier to write this type of code in python and be executed from java or javascript. The chapter ends with an excellent hands-on example using FastR, the GraalVM high-performance implementation of the R programming language.

Chapter 9 covers the rest of the languages such as WASM, Ruby, and the ones based on LLVM.

The chapter starts by covering the support for LLVM. Any source language can use this toolchain, as long as the source code be represented as an LLVM intermediate representation (IR). A lot of LLVM compilers exist today, for example, Clang (C, C++, and Objective C),

Swift, Rust, and Fortran. Sulong is an LLVM interpreter that is written in Java and internally uses the Truffle framework. Sulong enables all language compilers that can generate LLVM IR to run on GraalVM directly. The author shows the example of using c++ to run on GraalVM and later call the code from Java, then shows the other way around calling java from c++. If you’re a fan of Ruby, you will like TruffleRuby, a high-performance implementation of the Ruby programming language on GraalVM. Now you have the opportunity to use libraries gems from Java; the author shows an example running a ruby program from java and passing data between the languages using the truffle context programming model. In the second half of this chapter, the author explains how to run Wasm in the JVM, c++, and Rust are two languages supported. The authors show an example using emscripten, the compiler that allows converting a c/c++ program to web assembly, then taking the WASM output; once you have a .wasm file, you can use the GraalVM to run in the JVM.

Chapter 10 shows how to build microservices using different cloud-native frameworks. The chapter starts with a short description of microservices and benefits such as scaling them independently. It introduces how to scale them using containers and how these are different from using virtual machines. The author shows an example of a Book library application composed of different microservices; the example is very relatable to people building microservices today on the cloud. The microservice contains a front-end, caching services, prediction services, caching services, and persistence backend. The final code is in git; if you want to try it out, the author selects one of the microservices and then shows how to implement and run using various frameworks such as SpringBoot, Micronaut, Quarkus, and serverless fn project. This is the only place where a couple of pages mentions Quarkus. I had the wrong impression that GraalVM was equivalent to Quarkus but after reading the book now have a full understanding and the scope of Graal.

Closing Thoughts

I have to admit when I sat to read this book, I never expected the content that I got. My first impression just reading the title and the few things I heard about GraalVM was that this book was going to be about Quarkus and SpringBoot using GraalVM and how to build containers with them which I already have done, so maybe there was something to learn here that I could use at work. But boy, I was wrong; after reading this book, I’m so impressed that I would say that professors will mention this book on a lot of college campuses. I would predict that when a professor picks on this book, they will create a 3xx course, and the students would need to be grateful to the author for unpacking complex topics simply and elegantly with practical examples. In terms of production work for cloud-native applications, companies that pick up the contents of this book would benefit from taking it as part of their training on-boarding. GraalVM provides many benefits of mixing languages and saves time from rewriting code already written in other languages. It is the golden nugget I was not aware of that got out of this book.

--

--

Carlos Santana

Sr. Contaibers Specialist SA @ AWS Kubernetes, Knative, Istio, OpenShift, UX, Serverless, DevOps, GitOps, SRE, Architect, Speaker, CKA, CKAD, CKS