GraalVM vs JVM: Understanding Reflection, Performance, and the Future of Java

Java has been a dominant language for decades, largely due to its “write once, run anywhere” philosophy supported by the Java Virtual Machine(JVM). However, with the advent of GraalVM, a more versatile virtual machine that can compile Java into native executables, developers are starting to rethink how they run their Java applications. This shift becomes especially intriguing when considering reflection, a feature commonly used in modern frameworks like Spring. We will dive into how reflection is handled in the JVM, compare its performance with GraalVM, and analyze which approach is better for different use cases.

What is Reflection in Java?

Reflection in Java is the ability to inspect and manipulate classes, methods, and fields at runtime. This allows frameworks like Spring to scan classes for annotations(e.g., @Controller ,@Autowired ) and create or inject objects without knowing the exact class definition beforehand.

Example of Reflection in Java

import java.lang.reflect.Method;

class Hello {
    public void sayHello() {
        System.out.println("Hello, World!");
    }
}

public class ReflectionExample {
    public static void main(String[] args) throws Exception {
        Class<?> clazz = Hello.class;
        Method method = clazz.getMethod("sayHello");
        method.invoke(new Hello());  // Outputs: Hello, World!
    }
}

Here, we use reflection to call sayHello() method on the Hello class. Notice that this happens at runtime, meaning the class Hello and method sayHello are inspected dynamically.

Link detail to reflection : https://techtipsandtricks.org/detailed-blog-on-reflection-in-java/

How Spring Uses Reflection

Spring heavily relies on reflection for dependency injection, bean management, and handling annotations. For example, when you annotate a method with @GetMapping in a Spring Controller, Spring scans all methods at runtime, looks for that annotation, and maps the HTTP route to the method.

@RestController
public class MyController {
    
    @GetMapping("/hello")
    public String sayHello() {
        return "Hello, Spring!";
    }
}

Under the hood, Spring uses reflection to find all methods annotated with @GetMapping, stores the mapping, and handles HTTP requests by invoking the corresponding method via reflection.

The Cost of Reflection in JVM

Runtime Overhead

Reflection in the JVM is powerful but comes at a cost. Since it operates dynamically, it adds overhead at runtime, including:

  • Method Lookup: Finding methods via reflection is slower than direct method calls.
  • Metadata Storage: The JVM must store additional metadata for all classes to enable reflection.
  • Security Checks: Reflection bypasses access controls, potentially opening up security vulnerabilities.

For small applications, this overhead might not be significant, but in large, microservice-based architectures, especially those using Spring Boot, reflection can increase the start-up time and memory usage.

Performance Comparison: Direct Invocation vs Reflection

Let’s compare the performance of direct method invocation and reflection in the JVM:

public class ReflectionPerformanceTest {

    public static void main(String[] args) throws Exception {
        Hello hello = new Hello();
        
        // Direct invocation
        long startDirect = System.nanoTime();
        hello.sayHello();
        long endDirect = System.nanoTime();
        System.out.println("Direct Invocation: " + (endDirect - startDirect) + " ns");
        
        // Reflection invocation
        Method method = Hello.class.getMethod("sayHello");
        long startReflect = System.nanoTime();
        method.invoke(hello);
        long endReflect = System.nanoTime();
        System.out.println("Reflection Invocation: " + (endReflect - startReflect) + " ns");
    }
}

Reflection is significantly slower due to the additional processing required to look up and invoke the method dynamically.

GraalVM vs JVM: A Detailed Comparison

Both GraalVM and the traditional Java Virtual Machine (JVM) are environments for running Java applications, but they offer distinct features, capabilities, and performance trade-offs. In this comparison, we’ll cover what GraalVM is, its key differences compared to the JVM, and where each excels.

The implementation code example of simple rest api with reflection : https://github.com/hongquan080799/spring-boot-reflection-demo

What is GraalVM?

GraalVM is a high-performance runtime that provides significant improvements over the traditional JVM. Initially developed by Oracle Labs, GraalVM is designed to support not only Java but also other languages such as JavaScript, Python, Ruby, and LLVM-bases languages (e.g., C,C++). Its primary goal is to offer a universal virtual machine, allowing interoperability between languages and optimizing performance using modern compilation techniques.

Key Feature of GraalVM

  • AOT Compilation (Ahead-of-Time): GraalVM provides native image generation, allowing you to compile Java applications into standalone executable. These binaries start faster and consume less memory compared to traditional JVM applications.
  • Polyglot Capabilities: GraalVM can execute not only Java but also other languages like JavaScript, Python, Ruby, and more. These languages can interoperate efficiently within the same runtime.
  • JIT Compiler: GraalVM includes a Just-In-Time (JIT) compiler, which is an alternative to traditional JVM’s C2 compiler. Graal’s JIT compiler is written in Java, making it easier to maintain and optimize.
  • JavaScript Engine: GraalVM replaces the Nashorn JavaScript engine (which was deprecated in JDK 11) with a high-performance JavaScript engine.
  • Native Image: One of the most talked-about features of GraalVM is the Native Image functionality. This allows Java applications to be compiled ahead of time into native binaries, leading to smaller memory foot prints and faster startup times.

What is JVM?

The Java Virtual Machine(JVM) is the core runtime environment for executing Java applications. It allows Java programs (bytecode) to run on any platform without the need for recompilation, making Java platform-independent. The JVM also supports other languages like Kotlin, Scala, and Groovy that compile to Java bytecode.

Key features of JVM

  • Cross-Platform Execution: The primary role of the JVM is to provide a platform-independent environment for Java code. Once compiled into bytecode, Java can be run on any platform that has a compatible JVM.
  • Garbage Collection: The JVM automatically manages memory using garbage collection, reducing the need for manual memory management and avoiding memory leaks.
  • JIT Compilation: The JVM uses Just-In-Time (JIT) Compilation to convert bytecode into native machine code during runtime, improving the performance of Java applications.
  • HotSpot: The HotSpot JVM is widely used and optimized for performance. It uses both the C1 and C2 compilers to provide fast code execution.
  • Managed Runtime: The JVM manages many aspects of application execution, including memory allocation, thread management, and exception handling.

Key Differences Between GraalVM and JVM

  1. Startup Time
    • GraalVM (Native Image): GraalVM native images provide fast startup times. Since the application is precompiled into a native binary, there’s no need to wait for JIT compiler to warm up. This makes it ideal for cloud-native, serverless applications, or microservices where quick startup is crucial.
    • JVM: JVM-based applications have a slower startup time compared to GraalVM native images. This is because the JVM needs to load the application’s bytecode and then JIT-compile at runtime. However, once the JVM has warmed up, performance becomes more competitive.
  2. Memory usage
    • GraalVM (Native Image): Native images of GraalVM typically consume less memory compared to JVM applications, as they don’t include the overhead of the JVM runtime or JIT compilation. This is another reason why GraalVm native image is preferred for microservices.
    • JVM: The JVM is designed for long-running applications, and its memory usage grows as the JIT compiler optimizes the code over time. While it’s efficient for large, long-lived applications, it tends to use more memory than GraalVM native images, especially for short-lived processes.
  3. Performance (Warm vs. Cold)
    • GraalVM:
      • Native Image: Native image provides faster startup and lower memory usage but may have slightly lower peak performance compared to the JVM, as some runtime optimizations (like JIT compilation) are not possible.
      • JIT Mode: When running in JIT mode (like a traditional JVM), GraalVM can offer high performance and, in many cases, outperform the standard JVM
    • JVM: The JVM with HotSpot is highly optimized for long-running applications. Once the JIT compiler has warmed up and optimized the code, it often provides better peak performance than GraalVM’s native images.
  4. Ahead-of-Time (AOT) Compilation
    • GraalVM: Supports Ahead-of-Time(AOT) compilation, allowing Java applications to be compiled into native executables before they are run. This makes GraalVM a strong choice for applications where startup time and memory efficiency are important, such as in microservices or serverless environments.
    • JVM: The traditions JVM doesn’t support AOT compilation. All code is compiled at runtime using JIT, which means that there is always some startup overhead and runtime memory cost for JIT compiler.
  5. Polyglot Capabilities
    • GraalVM: One of the GraalVM’s key features is its ability to run multiple languages in the same environment. This includes Java, JavaScript, Ruby, Python, and even C/C++ (via LLVM). You can write parts of your application in different languages, and GraalVM allows them to interoperate seamlessly.
    • JVM: The JVM primarily supports Java and JVM-based languages like Scala, Kotlin, and Groovy. It does not natively support non-JVM languages, such as JavaScript or Python, though some interoperatebility is available though external libraries.
  6. Reflection Support
    • GraalVM: One of the main limitations of GraalVM’s native image is its incomplete support for reflection (use by frameworks like Spring and Hibernate). Developers often need to provide a reflect-config.json file to explicitly declare which classes and methods will use reflection. This can add complexity to applications that heavily rely on reflection.
    • JVM: Reflection is fully supported in the JVM. It’s one of the key features used in popular Java frameworks for dependency injection, annotations, and runtime configuration. There are no additional configurations needed to use reflection in the JVM.
  7. Application Use Cases
    • GraalVM
      • Microservices: Fast startup time and low memory usage make GraalVM native images a greate fit for cloud-native and microservice architechtures.
      • Serverless: Ideal for serverless environments where performance needs to be immediate.
      • Polyglot Applications: GraalVM is a good choice when you want to combine multiple programming languages in one environment.
    • JVM:
      • Long-running Applications: Applications that are expected to run for extended periods will benefit from JVM’s JIT optimization.
      • Enterprise Systems: The JVM is well-suited for large-scale, enterprise systems where peak performance is crucial after an initial startup phase.

References of using GraalVM with Spring: https://www.youtube.com/watch?v=YclrKfEUHrI

GraalVM vs JVM Performance comparation

In order to compare the performance, i gonna create a simple rest api service with connection to database and then monitor the performance

You can find the source code here: https://github.com/hongquan080799/asycnchonous-example/tree/main/SpringTest

I gonna use compile GraalVM to native image to run compare to run direct on JVM

To monitor the performance i will use the python script to get the CPU and Memory usage, source code here : https://github.com/hongquan080799/asycnchonous-example/blob/main/python/performance/performance.py

Find PID on windows and and run python script

netstat -aon | findstr 8000 

Result gonna be like this

GraalVM

JVM

In the comparation process, as you can see when run on GraalVM (Native Image) CPU and Memory consumption is used less than JVM (about 6 times less CPU and half memory consumption)

The Future of Java with GraalVM

As more developers adopt cloud-native architectures, GraalVM’s native image feature will play a critical role in shaping Java’s future. By reducing startup time and memory consumption, GraalVM is an ideal choice for running Java in containers, microservices, and serverless environments.

While GraalVM provides significant advantages in certain scenarios, it’s important to recognize that the traditional JVM is still highly optimized for long-running applications, particularly those that benefit from JIT optimizations. Both GraalVM and the JVM will likely coexist, with GraalVM driving innovation in performance-critical applications.

Conclusion: Which Should You Use?

When deciding between GraalVM and the traditional JVM, consider your use case:

  • Use GraalVM if:
    • You need fast startup times and low memory consumption (e.g., in microservices or serverless architectures).
    • You want to leverage polyglot capabilities or integrate multiple programming languages in one runtime.
    • You are building cloud-native or containerized applications.
  • Use JVM if:
    • You are working on long-running applications where the JIT compiler can fully optimize performance over time.
    • Your application heavily relies on dynamic reflection without pre-configuring it.

GraalVM is pushing Java into new frontiers of performance and language interoperability, but the traditional JVM still holds its ground as a highly optimized and mature platform for many existing applications.

By understanding the strengths and trade-offs of both platforms, developers can make informed decisions that suit their application needs, whether they choose the JVM for long-running processes or GraalVM for modern cloud-native workloads.

Leave a Comment

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

Scroll to Top