Build and run

jam-vm builds two things: a native jam backend and a patched JDK. The backend uses C++26 modules. The JDK uses its normal C++14 toolchain and calls the backend through a C header.

Build on macOS 26 arm64, Linux x86_64 or Windows 11 x86_64. See supported configurations for the current limits.

Tools

Install Git, Python 3, patch, and the normal OpenJDK platform build tools. On macOS that includes Xcode and its SDK. On Debian or Ubuntu, install the compiler and development headers with:

sudo apt-get install build-essential autoconf m4 zip unzip \
  libx11-dev libxext-dev libxrender-dev libxrandr-dev libxtst-dev libxt-dev \
  libcups2-dev libfontconfig1-dev libasound2-dev libfreetype-dev libnuma-dev \
  patchelf binutils

The tested toolchains use:

Tool Version or requirement
C++ compiler for jam LLVM 23.1.2
C++ library on Darwin libc++ 22.1.8, both headers and runtime, from Homebrew llvm@22
C++ library on Linux libc++ 23.1.2 from the LLVM distribution
C++ library on Windows MSVC 14.44 from Visual Studio 2022
CMake 4.4.3
Ninja 1.12 or newer
Autoconf 2.72
GNU M4 / Make 1.4.20 / 4.3 or newer
Boot JDK JDK 24 or 25; the recorded build uses GraalVM Community 25.3.4.1
HotSpot compiler Apple Clang 21, GCC 11 or MSVC 19.44, compiling as C++14

The Darwin build pairs LLVM 23's compiler with libc++ 22's headers and runtime. Linux uses the compiler and libc++ from the same LLVM 23 distribution. Keep the selected headers and runtime together.

The Unix build scripts default to tools under .toolchains/. Override their locations for your installation:

export JAM_CMAKE=/absolute/path/to/cmake
export JAM_NINJA=/absolute/path/to/ninja
export JAM_CXX=/absolute/path/to/clang++
export JAM_AUTOCONF=/absolute/path/to/autoconf
export JAM_M4=/absolute/path/to/m4
export JAM_MAKE=/absolute/path/to/gmake
export JAM_BOOT_JDK=/absolute/path/to/jdk-25

On macOS, use a JDK bundle's Contents/Home directory. JAM_LIBCXX_PREFIX defaults to /opt/homebrew/opt/llvm@22 there. On Linux, set it to the LLVM distribution directory containing include/c++/v1 and the C++ runtime under lib/. JAM_JOBS defaults to eight for the JDK and native backend, and three for GraalVM. The native test runner expects ctest next to the selected cmake binary.

Prepare the sources

git clone https://github.com/ekmett/jam-vm.git
cd jam-vm
python3 tools/fetch_sources.py --full
python3 tools/prepare_jdk.py

The manifest records the source revisions and archive hashes. Preparation applies the Windows hosting extension to the pinned Jam checkout and leaves upstream/native unchanged. It extracts OpenJDK into upstream/jdk25 and applies the HotSpot patch.

The JDK preparation script refuses to replace an existing source directory. Run it once in a fresh checkout. Repeated builds use the prepared sources.

Build the backend and JVM

Set JAM_BOOT_JDK before the native build so it also builds the JNI test bridge.

bash tools/build_native.sh
bash tools/build_hotspot.sh

The first command builds build-jam/ and runs the native tests. The second configures and builds a fastdebug JDK with jamgc, epsilongc and serialgc. Jam uses Serial's block-offset-table utility, so the Serial build feature is required even when Jam is the selected collector.

Use the completed image's bin/java; the path helper selects the host platform:

export JAM_JAVA="$(python3 tools/platform_paths.py)/bin/java"
"$JAM_JAVA" -Xshare:off -Xms256m -Xmx256m \
  -XX:+UnlockExperimentalVMOptions -XX:+UseJamGC -Xlog:gc \
  -jar application.jar

Startup logging should identify Using Jam. Both heap limits must be equal. The collector validates compressed oops, ordinary object headers, eight-byte object alignment and its reserved address windows before allocating objects. -Xshare:off makes the lack of archived Java heap support explicit.

JamYoungSize selects usable nursery bytes; zero chooses one quarter of the heap. JamPromoteEvery selects the interval between whole-nursery promotion attempts, defaulting to three minors. JamWorkers selects jam's worker count; the recorded VM tests use four. HotSpot object scanning currently runs on the VM thread while jam's copy work can run in parallel.

GraalVM

For Truffle languages, build the GraalVM variant. It pairs LabsJDK with Graal 25.3.4.1, the release used by thc. Both the VM and compiler need the Jam patch; putting stock libgraal beside a Jam-enabled JDK is not sufficient.

Starting from a fresh checkout, with the tools above configured:

python3 tools/fetch_sources.py --graal
python3 tools/prepare_jdk.py --graal
python3 tools/prepare_graal.py
export JAM_HOTSPOT_SOURCE="$PWD/upstream/labsjdk25"
bash tools/build_native.sh
bash tools/build_hotspot.sh --graal
bash tools/build_graal.sh

This builds build/graalvm/, including patched libgraal, the Jam backend and the weak API. Use it as JAVA_HOME and select Jam as above. The compiler uses Jam's card table for old-to-young stores. Compressed oops remain enabled.

The build uses the pinned mx checkout and keeps downloaded build dependencies in .toolchains/mx-cache/. JAM_GRAAL_OUTPUT selects another output directory; the packaging step refuses to overwrite an existing installation.

The distribution also includes the SubstrateVM adapter. Select it with native-image --gc=jam when building a native executable. See Native Image for heap sizing, deployment and the weak API.

Development checks

bash tools/check_vm.sh
python3 tools/check_gc_registration.py
build-jam/jam-generational-test --require-simd
python3 tools/check_patches.py

check_vm.sh uses JAM_JAVA when set, with javac next to it; JAM_JAVAC overrides that choice. Without overrides it uses the host platform's fastdebug JDK image. These checks exercise the public API, Java reference behavior, barriers and generation transitions. Generated logs stay local and are ignored by Git. check_patches.py checks the Jam extension against its pin and reconstructs the HotSpot sources from the patch.

Packaging

The GraalVM build already packages its runtime. To package the plain JDK:

jdk="$(python3 tools/platform_paths.py)"
python3 tools/build_bridge.py --java-home "$jdk"
python3 tools/package_jdk.py --java-home "$jdk" --output build/jam-jdk

The output is a JDK home that can be moved out of the checkout. lib/jam/ contains the backend, JNI bridge, Java API and C++ runtime libraries. The packager rewrites native library paths relative to the installation; on macOS it also signs the modified binaries for local use. It preserves upstream licenses under legal/. The runtime prefix must contain LICENSE.TXT covering its bundled C++ libraries. Linux packages keep glibc and other OS libraries as system dependencies, so deploy on a compatible architecture and glibc version. The Linux toolchain script fetches the runtime licenses from the matching LLVM source revision.

For the weak API, put lib/jam/jam-vm.jar on the application's class path and lib/jam on java.library.path. See integrating thc for registration and finalizer pumping.

Windows

Install Git, Python 3.10 or newer, and Visual Studio 2022 with the C++ build tools and Windows SDK. Enable Win32 long paths from an elevated PowerShell:

Set-ItemProperty 'HKLM:/SYSTEM/CurrentControlSet/Control/FileSystem' `
  -Name LongPathsEnabled -Type DWord -Value 1

Start a new shell after changing the setting. The Graal dependency cache contains paths longer than the legacy 260-character limit. Use a checkout path without spaces. From PowerShell, the dependency script installs the remaining tools into a directory you choose:

$env:JAM_CI_TOOLS = "$PWD/.toolchains/windows"
./tools/ci/setup_windows.ps1 hotspot
python tools/fetch_sources.py --full
python tools/prepare_jdk.py
./tools/build_native.ps1
./tools/build_hotspot.ps1

Jam uses clang-cl and the MSVC C++ library. HotSpot uses MSVC directly. Cygwin supplies OpenJDK's shell and build tools; the resulting JVM is a native Windows program. In a new PowerShell session, restore the tool environment with . "$env:JAM_CI_TOOLS/env.ps1".

Package the JDK before moving it or running it outside the build environment:

$jdk = python tools/platform_paths.py
$env:Path = "$PWD/build-jam;$env:Path"
python tools/build_bridge.py --java-home $jdk
python tools/package_jdk.py --java-home $jdk --output build/jam-jdk `
  --runtime-license "$env:JAM_CI_TOOLS/Microsoft-Build-Tools-License.docx" `
  --runtime-license "$env:JAM_CI_TOOLS/Microsoft-Redistribution.md"
./build/jam-jdk/bin/java.exe -Xshare:off -Xms256m -Xmx256m `
  -XX:+UnlockExperimentalVMOptions -XX:+UseJamGC -jar application.jar

The package puts Jam and its runtime DLLs in bin/, and keeps the Native Image inputs and Java API in lib/jam/. The MSVC redistribution documents and LLVM compiler-runtime license travel with the package. For a manually installed toolchain, set JAM_MSVC_REDIST to its x64/Microsoft.VC143.CRT directory and JAM_COMPILER_RUNTIME_LICENSE to the matching compiler-rt license.

For GraalVM, start in a fresh checkout:

./tools/ci/setup_windows.ps1 graal
python tools/fetch_sources.py --graal
python tools/prepare_jdk.py --graal
python tools/prepare_graal.py
$env:JAM_HOTSPOT_SOURCE = "$PWD/upstream/labsjdk25"
./tools/build_native.ps1
./tools/build_hotspot.ps1 -Graal
python tools/build_graal.py

Use build/graalvm/bin/java.exe for the JVM and build/graalvm/bin/native-image.cmd --gc=jam for native executables. Windows class paths use ; between entries.

Rebuilding

HotSpot's collector registration is compiled into libjvm. A stock JVM cannot discover Jam through JNI, JVMTI or -agentpath. Once the adapter is present, an ABI-compatible backend change can rebuild just the library. Changes to VM registration, barriers or the host contract require a HotSpot rebuild. Module BMI files are build inputs; they are not runtime dependencies.