JVM Workhorses - Stubs and Intrinsics

Tue, 17 Mar 2026 10:35:00 +0000
Java, JVM, Java Internals, Intrinsics, Stubs

A few weeks ago, I was analyzing a hot path in my current project. The code contained several calls to the following method:

System.arracopy(args...)

Building on this observation, I suspected this pattern might be suboptimal, assuming a heavy native library call was required to perform the copy. This assumption was based on the method signature:

public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);

However, after examining the C2-compiled machine code, I observed several calls to a Final Stub.

Given these findings, in this article, I will explain what stubs are and the vital role they play within the JVM.

Environment

I performed the analysis using the following JDK version: OpenJDK Runtime Environment Temurin-25.0.2+10 (build 25.0.2+10-LTS). The environment consisted of an Intel(R) Xeon(R) @ 2.20GHz - 4 vCPU (2 cores), 16GB RAM, running on Debian GNU/Linux 12 (bookworm).

The full source code is available on GitHub. For testing, I used the Adoptium mirror of the OpenJDK repository with the tag jdk-25.0.2+10.

Stubs

Stubs are pieces of assembly code generated and optimized to perform specific tasks, such as cryptographic functions, mathematical operations, and array copying. These are generated during JVM startup.

In the init.cpp file, we can find the following function calls:

// stub routines in initial blob are referenced by later generated code
initial_stubs_init();
...
continuation_stubs_init(); // depends on continuations_init
...
compiler_stubs_init(false /* in_compiler_thread */); // compiler's intrinsics stubs
final_stubs_init();    // final StubRoutines stubs

This demonstrates that stub routine generation is divided into several independent steps:

Initial stubs

Continuation stubs

Compiler stubs

Final stubs

Because certain stubs depend on others, this sequence is critical for correct generation.

We can inspect the time required to generate stubs using the following command:

scala-cli . --main-class playground.Main --java-opt -Xlog:startuptime --power

The output displays the following:

[0.005s][info][startuptime] StubRoutines generation initial stubs, 0.0005537 secs
...
[0.019s][info][startuptime] StubRoutines generation continuation stubs, 0.0000309 secs
...
[0.026s][info][startuptime] StubRoutines generation final stubs, 0.0003121 secs
...
[0.041s][info][startuptime] StubRoutines generation compiler stubs, 0.0033504 secs

Generation is remarkably efficient. The longest phase - compiler stub generation - took approximately 3 ms, while the other phases were completed in microseconds.

We can also examine the generated assembly code by enabling the -XX:+PrintStubCode option. The output for this command is extensive. I have included it in the GitHub repository for those who wish to perform a closer inspection.

According to the documentation in stubRoutines.hpp, although each step (initial, final, etc.) can produce multiple stubs, they are generated within a single BufferBlob. Each BufferBlob simply contains multiple entry points. This differs from the stubs (also called blobs) generated by SharedRuntime, which are bundled into separate BufferBlobs.

A BufferBlob is a container for machine code stored in the Code Cache.

Stubs possess another interesting characteristic that enhances their efficiency: some stubs may be compiled using intrinsics.

Intrinsics

Intrinsics can be thought of as specialized assembly code written directly for a specific CPU architecture. An intrinsic function utilizes specific Instruction Set Architecture...