Java

Java

Java

Topics on Semeru (Java) on IBM Z

 View Only

Running x86 Java 21 Applications on IBM Z (s390x) Using QEMU: A Practical Walkthrough

By Alex Osadchyy posted Mon January 26, 2026 08:55 PM

  

By Alex Osadchyy, Dimitar Yankov, Peter Tsenkov, Elissa Covarubias,  IBM Z ISV Ecosystem.

1. Why This Matters

In today's fast-evolving platforms, where high-performance processing is critical, IBM's s390x architecture (IBM Z and LinuxONE) stands out for enterprise workloads. It delivers unmatched scalability, resource consolidation, and reliability, often running workloads from thousands of x86_64 servers on a single system, reducing costs, energy use, and enhancing security. The latest IBM Z systems (e.g., z16 and beyond) feature an integrated on-chip AI accelerator, enabling low-latency AI inference within transactions, boosting performance for AI-enhanced Java applications, such as real-time fraud detection.

On s390x, a recurring challenge is deploying modern Java applications when vendors only provide x86_64 RPM installers or other non-essential parts, such as configurators. While the application itself may be written in Java, the installation artifacts are architecture-specific and cannot run natively on IBM Z/LinuxONE.

QEMU system emulation provides a practical workaround. By installing the application on an emulated x86 VM, teams can extract and mirror the required JAR files, configuration, and service definitions onto the target s390x system, using QEMU as a compatibility bridge rather than a production runtime.

Although Java is architecture-agnostic, Java 21 aggressively optimizes for modern CPUs. JVM's HotSpot C2 compiler can automatically emit AVX, AVX2, and AVX-512 instructions for certain operations like array manipulation and string operations. Emulating these instructions with QEMU can be challenging, both from a performance perspective and in terms of JVM stability if CPU feature support is incomplete.

This walkthrough focuses on setting up and validating this approach safely - ensuring reasonable behavior under emulation, avoiding JVM incompatibilities, and understanding the limits before moving the workload to a native s390x Java runtime.

2. The High-Level Architecture

a) Install time

Java application is installed using x86 installer on QEMU x86 on IBM Z (s390x host).

b) Mirror time

Java classes and JAR files are mirrored from x86_64 virtual machine onto the underlying IBM Z (s390x host).

c) Run time

Java application is deployed on IBM Z (s390x host) and runs natively on JVM on IBM Z (s390x host).

3. Building QEMU for x86_64 Emulation on s390x

To run an x86_64 virtual machine on an s390x host, QEMU must be built from source with the x86_64 system emulator enabled. The steps below compile only what’s needed, keeping the build minimal and focused. Also the steps were executed on RHEL 9.6 and may need adjustments for other distros.

First, install pre-reqs and clone the QEMU source repository and move into it:

sudo dnf group install "Development Tools"

Sudo dnf config-manager --set-enabled codeready-builder-for-rhel-9-s390x-rpms

sudo dnf install python3-sphinx-rtd-theme

sudo dnf install glib2-devel

sudo dnf install libslirp.s390x

sudo dnf install meson pkgconf libaio-devel libcap-ng-devel libiscsi-devel libnfs-devel libusb1-devel gnutls-devel curl-devel vte291-devel usbredir-devel brlapi-devel capstone-devel device-mapper-multipath-devel libjpeg-turbo-devel lzo-devel numactl-devel rdma-core-devel libseccomp-devel snappy-devel virglrenderer-devel xfsprogs-devel libzstd-devel dtc

sudo dnf install python3-tomli python3-sphinx ninja-build genisoimage

libslirp has to be compiled as the version in the test is 4.4 while QEMU requires minimum of 4.5.

wget https://gitlab.freedesktop.org/slirp/libslirp/-/archive/v4.5.0/libslirp-v4.5.0.tar

tar xvf libslirp-v4.5.0.tar cd libslirp-v4.5.0

meson build ninja -C build install

git clone https://gitlab.com/qemu-project/qemu.git
cd qemu

git checkout stable-9.2

Note: QEMU 10 was not compatible with RHEL 9.6, needed to switch to QEMU 9.

Next, configure QEMU to build the x86_64 system emulator (x86_64-softmmu). The --enable-slirp option enables user-mode networking, which is convenient for quick setups without requiring TAP interfaces or root-level network configuration:

./configure --target-list=x86_64-softmmu --enable-slirp

Build QEMU using the default make process:

make

Finally, install the compiled binaries onto the system:

sudo make install

After installation, the qemu-system-x86_64 binary should be available and ready to launch an x86_64 virtual machine on the s390x host.

Export the shared qemu library for the session:

export LD_LIBRARY_PATH=/usr/local/lib:/usr/local/lib64:$LD_LIBRARY_PATH

And make it permanent by adding the line above to your ~/.bashrc or  /etc/ld.so.conf.d/local-qemu.conf + sudo ldconfig.

After that, verify qemu runs successfully

qemu-system-x86_64 --version

QEMU emulator version 9.2.4 (v9.2.4)

Copyright (c) 2003-2024 Fabrice Bellard and the QEMU Project developers

4. Preparing the Guest OS (Rocky Linux qcow2)

Rocky Linux is a stable RHEL clone available for multiple architectures, including s390x. For this walkthrough, we use it as the guest OS because its GenericCloud qcow2 images are lightweight, cloud-friendly, and ready for quick VM deployment.  These images do not require a subscription, making them ideal for temporary QEMU VMs used for installation and testing of the application rather than actual run in production. It’s essentially a short-term, pre-production step before mirroring the Java application to the actual s390x system.

Download the latest Rocky Linux x86_64 image:

wget https://dl.rockylinux.org/pub/rocky/9/images/x86_64/Rocky-9-GenericCloud-Base.latest.x86_64.qcow2

This image will serve as the base for our QEMU-emulated x86_64 VM, ready for installing Java and the application.

5. Bootstrapping the VM with Cloud-Init

After downloading the Rocky Linux qcow2 image, need to preconfigure the VM user and password using cloud-init. This allows to SSH in without manually setting up accounts.

Step 5.1: Create Cloud-Init Config Files

user-data – defines the user and password. Example:

#cloud-config

hostname: app-rocky-vm

users:

   - name: rocky

sudo: ALL=(ALL) NOPASSWD:ALL

groups: wheel

shell: /bin/bash

lock_passwd: false

plain_text_passwd: rocky1234

ssh_authorized_keys:

- ssh-ed25519 AAAAC3Nza....<obfuscated>

ssh_pwauth: true

disable_root: false

growpart:

mode: auto

devices: ['/']

resize_rootfs: true

bootcmd:

- [ cloud-init, clean ]

- [ rm, -rf, /var/lib/cloud/instance, /var/lib/cloud/instances/* ]

- [ touch, /var/lib/cloud/instance/boot-finished ]

meta-data – defines the meta-date for the instance

instance-id: app-rocky-vm-01

local-hostname: app-rocky-vm

Step 5.2: Package as an ISO

Use genisoimage to create a seed ISO that QEMU can attach at boot:

genisoimage -output seed.iso -volid cidata -joliet -rock user-data meta-data

-volid cidata → required for cloud-init to recognize the ISO

-joliet -rock → filesystem compatibility options

6. Starting the VM with qemu-system-x86_64

To start the x86_64 Rocky Linux VM on s390x, run QEMU with software emulation (TCG) and attach the cloud-init ISO for automatic user setup. The command below boots the VM with 3 GB RAM, forwards SSH to host port 2222, and enables modern CPU features for Java testing:

LD_LIBRARY_PATH=/usr/local/lib:/usr/local/lib64 /usr/local/bin/qemu-system-x86_64 -cpu max,-avx512f,-avx512dq,-avx512cd,-avx512bw,-avx512vl,-avx512vnni,-avx512bitalg -m 3G -drive file=Rocky-9-GenericCloud-Base.latest.x86_64.qcow2,if=virtio,format=qcow2,cache=writeback -drive file=seed.iso,if=virtio,media=cdrom -netdev user,id=net0,hostfwd=tcp::2222-:22 -device virtio-net-pci,netdev=net0 -display default -serial stdio

Available CPU flags for this QEMU startup: fpu de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush acpi mmx fxsr sse sse2 ss syscall nx mmxext pdpe1gb rdtscp lm 3dnowext 3dnow nopl cpuid pni pclmulqdq monitor ssse3 fma cx16 sse4_1 sse4_2 x2apic movbe popcnt aes xsave avx f16c rdrand hypervisor lahf_lm svm cr8_legacy abm sse4a 3dnowprefetch vmmcall fsgsbase bmi1 avx2 smep bmi2 erms mpx rdseed adx smap clflushopt clwb sha_ni xsaveopt xgetbv1 xsaveerptr wbnoinvd arat npt vgif umip pku ospke vaes la57 rdpid fsrm

7. Accessing the VM and Installing Java

Once the VM is running with cloud-init, you can SSH into it using the forwarded port:

ssh rocky@localhost -p 2222

The username rocky comes from the cloud-init configuration in user-data-centos. Once logged in, update the package metadata and install OpenJDK 21:

sudo dnf update -y
sudo dnf install -y java-21-openjdk java-21-openjdk-devel

Verify the installation:

java -version

You should see output confirming OpenJDK 21 is installed and ready. The VM is now prepared for running Java applications that leverage modern CPU features such as AVX, AVX2, and AES.

Verify the JVM and CPU feature visibility

Step 7.1 Use the -XshowSettings:properties option to see JVM and system info:java -XshowSettings:properties -version

Look for lines like:

sun.arch.data.model = 64
os.arch = amd64

This confirms the JVM recognizes an x86_64 CPU.

Step 7.2 Check supported vector and SIMD instruction sets:

OpenJDK 21 includes the Vector API and -XX:+PrintFlagsFinal can help inspect CPU features:

java -XX:+UnlockDiagnosticVMOptions -XX:+PrintFlagsFinal -version | grep -i avx

Example output might show flags like:

int UseAVX = 2

int UseSSE = 4

intx MaxVectorSize = 16

These indicate the CPU features the JVM is aware of. In QEMU/TCG emulation, these may be reported even though execution is software-emulated.

8. A Java Workload That Cares About CPU Features

What we experimented and documented here is a practical way to validate CPUsensitive behavior of newer Java runtimes (JVM 21+) under emulation. The actual application that was used in this project is not disclosed here due to confidentiality terms. Instead, we re-produced the same practice of running on small sample Java workload that uses the Vector API, which triggers SIMD code generation by the HotSpot JIT. Even under QEMU, the JVM emits AVX or SSE instructions based on the advertised CPU flags. Running QEMU on s390x with emulated flags allowed functional testing of vectorized operations. A simple arraymath example with FloatVector.SPECIES_PREFERRED reliably demonstrated JIT vectorization. Diagnostic flags such as -XX:+PrintCompilation, -XX:+PrintIntrinsics, and -XX:+PrintAssembly confirmed SIMD paths are selected. This workload verified correctness, not speed.  Suitable for running non-critical x86 applications as-is (such as installation) along with porting the actual core applications to execute natively on s390x architecture.

9. Closing Thoughts
 QEMU provides a practical bridge for teams that must install x86only Java distributions or run non-essential x86only Java workloads along with deploying the core workload natively on IBM Z. This workflow leverages Java’s portability, allowing applications to be fast-track validated with x86 emulation while ultimately running core workloads on the s390x JVM with its own optimized JIT and hardware accelerators. It also eliminates delays caused by vendors that have not yet published native installers for IBM Z. Such Java applications often can run as-is on s390x platform, however the installer available for x86 only platform prevents users from deploying it on s390x. QEMU allows mixedarchitecture execution a smoother migration path and more predictable adoption of IBM Z for enterprise Java workloads.

0 comments
22 views

Permalink