HomeCommunityTools, Software and IDEs blog
August 5, 2026

Vector-related improvements in JDK 21 and JDK 25 compared with JDK 17 on AArch64

Discover how JDK 21 and JDK 25 improve AArch64 performance with enhanced C2 auto-vectorization, NEON, SVE, and new JVM intrinsics

By Fei Gao

Share
Reading time 21 minutes

Executive summary

Java developers running workloads on Arm may be able to unlock meaningful performance gains simply by upgrading the JDK. Compared with JDK 17, JDK 21 and JDK 25 enable HotSpot to generate more efficient AArch64 code through improved C2 auto-vectorization and new NEON- and SVE-accelerated intrinsics, often without requiring any application source changes.

The performance gains depend on the workload, CPU features, vector length, data types, and loop shapes. This blog highlights representative examples where newer JDK releases can generate more efficient SIMD code, helping developers identify workloads where an upgrade is worth evaluating.

Overview 

The target audience for this blog is developers who ask : 

“What vector-related runtime performance improvements can we get by upgrading the JDK?” 

Modern OpenJDK releases provide several ways to use AArch64 SIMD hardware (NEON and SVE) to improve application performance. These mechanisms fall into 3 main categories: 

1. JIT Vectorization 

The top-tier C2 JIT compiler can automatically vectorize eligible scalar loops. It transforms them into NEON or SVE vector instructions. These optimizations require no application code changes and continue to improve as the JDK evolves. 

2. Vector API 

The Vector API is  currently available as an incubator module. It provides explicit and portable SIMD programming capabilities at the Java language level. Applications can gain additional performance benefits by adopting the API, although this requires code changes. This blog focuses on automatic vectorization and intrinsics rather than explicit Vector API programming. 

3. JVM intrinsics on AArch64 

Selected JDK library methods use AArch64 intrinsics, allowing applications to benefit from SIMD acceleration by upgrading to a newer JDK version. 

For some workloads, upgrading OpenJDK can improve performance without changing application source code. More code becomes eligible for auto-vectorization, and more library methods use vectorized intrinsic implementations. 

The rest of this blog highlights the key improvements introduced in JDK 21 and JDK 25 compared with JDK 17. The generated code shown below represents specific benchmark shapes and hardware configurations. Actual code generation can vary based on CPU features, vector length, loop shape, data types, and profitability heuristics. 

Tip: If you are evaluating the auto-vectorization improvements described in this article, JDK 25 provides the diagnostic option -XX:+UnlockDiagnosticVMOptions -XX:AutoVectorizationOverrideProfitability=2. This option helps you compare the generated code with and without the default profitability heuristics to determine whether C2 can vectorize a loop more aggressively. It is intended for performance investigation rather than production tuning.

Improvements in JDK 21 

Induction-variable-dependent loop 

Some loops use the loop index as part of the computation. In the following example , the loop index i  is multiplied by each array element: 

void induction_dependent(int[] ia, int[] ib, int len) {  

    for (int i = 0; i < len; i++) {  

        ib[i] = ia[i] * i;  

    }  

}  

Arm CPUs with SVE support can efficiently generate a sequence of increasing numbers directly in vector registers, for example, 0, 1, 2, 3, and so on. JDK 21 can use this hardware feature to automatically vectorize loops like this one. 

By comparison, JDK 17 generates scalar code for the same loop: 

LOOP:  

  sbfiz    x11, x14, #2, #32  

  add      x17, x10, x11  

  ldr      w12, [x17, #16]  

  mul      w13, w12, w14  

  add      x0, x2, x11  

  str      w13, [x0, #16]  

  ...  

  add      w14, w14, #0x8   

  cmp      w14, w18  

  b.lt     LOOP  

JDK 21 generates SVE vector instructions that process multiple elements in parallel, improving throughput on SVE-enabled systems. For example, on a system with 128-bit SVE vectors, JDK 21 generates assembly code  such as the following: 

LOOP:  

  sbfiz  x11, x0, #2, #32  

  add    x12, x17, x11   

  ldr    q16, [x12, #16]  

  index  z17.s, w0, #1  

  mul    v16.4s, v17.4s, v16.4s  

  add    x11, x18, x11  

  str    q16, [x11, #16]  

  ...  

  add    w0, w0, #0x20  

  cmp    w0, w10  

  b.lt    LOOP  

Type conversion 

JDK 17 did not support auto-vectorization for type-conversions , even though AArch64 provides vector conversion instructions for both NEON and SVE. JDK 21 adds vectorization support for many type conversions, improving data format conversion performance. 

The following table summarizes the supported conversions for the NEON and SVE backends. 

Conversions marked NEON are supported on both NEON and SVE, while entries marked as SVE require SVE support: 

From/To  

byte  

short  

int  

long  

float  

double  

byte  

-  

-  

-  

SVE  

NEON  

SVE  

short  

-  

-  

-  

SVE  

NEON  

SVE  

int  

-  

-  

-  

NEON  

NEON  

NEON  

long  

SVE  

SVE  

NEON  

-  

SVE  

NEON  

float  

NEON  

NEON  

NEON  

NEON  

-  

NEON  

double  

SVE  

SVE  

SVE  

NEON  

NEON  

-  

For example, consider the following loop: 

void float2double(float[] fa, double[] da, int len) {  

  for (int i = 0; i < len; i++) {  

      da[i] = (double) fa[i];  

  }  

}  

JDK 17 generates scalar code for each conversion: 

LOOP:  

  sxtw   x15, w2  

  add    x17, x20, x15, uxtx #2  

  ldr    s17, [x17, #16]  

  fcvt   d17, s17  

  add    x15, x18, x15, uxtx #3  

  str    d17, [x15, #16]  

  ...  

  add    w2, w2, #0x8  

  cmp    w2, w14  

  b.lt   LOOP  

Instead of converting values one at a time, JDK 21 generates SIMD code that converts multiple array elements in parallel using NEON or SVE vector instructions. This improves the throughput of data-processing loops like this one: 

LOOP:  

  sxtw   x13, w0  

  add    x18, x15, x13, uxtx #2  

  ldr    d16, [x18, #16]  

  fcvtl  v16.2d, v16.2s  

  add    x13, x17, x13, uxtx #3  

  str    q16, [x13, #16]  

  …  

  add    w0, w0, #0x8  

  cmp    w0, w14  

  b.lt   LOOP  

Math functions 

JDK 21 adds auto-vectorization support for several commonly used math operations on AArch64 by using NEON or SVE where available. Supported operations include: 

  • Math.max(int, int) and Math.min(int, int) 

  • Math.round(double) to long and Math.round(float) to int 

  • Math.signum(double) and Math.signum(float) 

For example:

void math_min(int[] ia, int[] ib, int[] ic, int len) {  

  for (int i = 0; i < len; i++) {  

      ic[i] = Math.min(ia[i], ib[i]);  

  }  

}  

JDK 17 cannot vectorize this loop, so it processes one element at a time with scalar instructions: 

LOOP:  

  sbfiz    x12, x17, #2, #32  

  add    x14, x2, x12  

  add    x13, x10, x12  

  ldr    w18, [x14,#16]  

  ldr    w16, [x13,#16]  

  cmp    w16, w18  

  csel    w16, w16, w18, lt  

  add    x12, x3, x12  

  str    w16, [x12,#16]  

  ...  

  add    w17, w17, #0x4  

  cmp    w17, w6  

  b.lt    LOOP  

In contrast, JDK 21 generates SIMD code. The following loop uses vector loads, a vector minimum instruction, and a vector store to process multiple int elements in parallel: 

LOOP:  

  mov    w13, w11  

  sbfiz  x11, x13, #2, #32  

  add    x17, x14, x11  

  add    x18, x2, x11   

  ldr    q16, [x17,#16]  

  ldr    q17, [x18,#16]  

  smin   v16.4s, v16.4s, v17.4s  

  add    x11, x19, x11  

  str    q16, [x11,#16]  

  ...  

  add    w11, w13, #0x20   

  cmp    w11, w16  

  b.lt   LOOP  

More efficient vector code for reduction loop 

Consider a loop like this: 

int reduction(int[] ia, int[] ib, int len) {  

    int total = 0;  

    for (int i = 0; i < len; i++) {  

        int v = (ia[i] * ib[i]);  

        total += v;  

    }  

    return total;  

}  

This is a reduction loop. The variable total is updated on each iteration by adding a new value to it. 

JDK 17 already vectorizes this loop, but it repeatedly sums vector results back into scalar values during execution. This creates extra work and limits the available parallelism the CPU can exploit. 

JDK 21 improves this by keeping the accumulated values in vector registers for longer. 

Although total appears to depend on its previous value, each value added to it is independent. For example, the value added when i = 10 does not depend on the value added when i = 9. When no strict order is required, the JIT can process many iterations in parallel, accumulate the intermediate results in vector registers, and perform the final summation only once after the vector loop finishes. 

Instead of reducing each group of vector results inside the loop, JDK 21 accumulates them in a temporary vector register: 

 movi    v16.4s, #0x0  

  mov    v17.16b, v16.16b  

  

LOOP:  

  mov    w20, w10  

  sbfiz    x10, x20, #2, #32  

  add    x11, x22, x10  

  add    x10, x16, x10  

  ldr    q18, [x11, #16]  

  ldr    q19, [x10, #16]  

  mla    v17.4s, v18.4s, v19.4s  

  ...  

  ldr    q18, [x11, #128]  

  ldr    q19, [x10, #128]  

  mla    v17.4s, v18.4s, v19.4s   

  add    w10, w20, #0x20  

  cmp    w10, w12  

  b.lt    LOOP  

The final reduction is then performed once after the loop: 

   addv    s18, v17.4s  

    mov    w11, v18.s[0]  

    add    w11, w11, w19  

This reduces the amount of work inside the loop and removes the repeated scalar update of total, allowing the CPU to make better use of SIMD parallelism. 

This optimization applies only when the reduction operation can be safely reordered. This includes integer and long reductions that use addition, multiplication, bitwise AND, OR, or XOR, and minimum or maximum operations. It also applies to floating-point minimum and maximum reductions. Floating-point addition and multiplication reductions are not eligible because reordering these operations can change the final result due to floating-point rounding . JDK 25 adds support for long minimum and maximum  reductions. 

More advanced algorithm to handle loop dependencies 

Sometimes a loop cannot be fully vectorized, but parts of it can still benefit from SIMD acceleration. When vectorizing these loops, the compiler must handle dependencies between scalar and vector operations within the same loop. A less capable vectorization scheduler must take a conservative approach and avoid vectorizing the loop . 

JDK 21 improves the auto-vectorization scheduling algorithm to track memory dependencies between scalar and vector operations more accurately. This enables the C2 compiler to safely reorder independent operations and generate partial vector code where it improves performance. 

As a result, JDK 21 can automatically vectorize a more loops than JDK 17. 

For example: 

static void partial_vectorizing(float[] fa, float[] fb, int[] ia, int[] ib, int len) {  

  for (int i = 0; i < len; i += 2) {  

      ia[i + 0] = (int)(fa[i + 0] + 3.0f);  // A  

      fb[i + 0] = ib[i + 0];  // B  

      fb[i + 1] = ib[i + 1];  // C  

      ia[i + 1] = (int)(fa[i + 1] * 9.0f);  // D  

  }  

}  

Prior to JDK 26, C2 conservatively assumes that arrays of the same type might alias at compile time. For example, ia and ib may refer to the same array, and the same applies to fa and fb. 

During auto-vectorization dependency analysis, the compiler introduces dependency edges between the operations: B depends on A, and D depends on C. Because of these dependencies, JDK 17 cannot effectively vectorize the loop. 

However, JDK 21, can identify the operations that are safe to vectorize and generate a mix of scalar and vector instructions. 

After loop unrolling, the compiler generates operations such as: 

     ia[i + 0] = (int)(fa[i + 0] + 3.0f);  // A0  

      fb[i + 0] = ib[i + 0];  // B0  

      fb[i + 1] = ib[i + 1];  // C0  

      ia[i + 1] = (int)(fa[i + 1] * 9.0f);  // D0  

  

      ia[i + 2] = (int)(fa[i + 2] + 3.0f);  // A1  

      fb[i + 2] = ib[i + 2];  // B1  

      fb[i + 3] = ib[i + 3];  // C1  

      ia[i + 3] = (int)(fa[i + 3] * 9.0f);  // D1  

A and D are not vectorized because they are not isomorphic. However, B and C have the same computation pattern, so the compiler can combine them into vector operations. 

To do this, the compiler must also reorder the surrounding scalar operations. The A operations must remain before the vectorized B and C operations, while the D operations must remain after them. The compiler therefore moves all A operations before the vector block and all D operations after it. 

One valid reordered sequence is: A0 A1 [B0, C0, B1, C1] D0 D1. 

After reordering, the compiler can safely combine [B0, C0, B1, C1] into vector instructions and execute them in parallel. This enables JDK 21 to partially vectorize the loop while preserving the original program semantics and improving performance. 

Optimize exclusive OR 

The optional FEAT_SHA3 extension adds the eor3 instruction. eor3 performs an exclusive OR of three vectors. This benefits applications with consecutive eor operations because the compiler can combine them into fewer operations using the eor3 instruction. 

For a loop like: 

void eor(int[] ia, int[] ib, int[] ic, int[] id, int len) {  

  for (int i = 0; i < len; i++) {  

      id[i] = ia[i] ^ ib[i] ^ ic[i];  

  }  

}  

JDK 17 can already vectorize this loop using NEON vector eor instructions. The generated loop body contains two separate operations: 

  eor    v16.16b, v17.16b, v16.16b 

  eor    v16.16b, v16.16b, v18.16b 

JDK 21 further improves the generated code by recognizing this pattern and using a single eor3 instruction instead: 

  eor3   v16.16b, v18.16b, v16.16b, v17.16b 

Bit Manipulation Operations 

JDK 21 adds auto-vectorization support for a variety of bit-manipulation operations on AArch64. 

AArch64 C2 backend can vectorize these using NEON or SVE instructions: 

Integer.numberOfTrailingZeros() 

Integer.numberOfLeadingZeros() 

Long.bitCount() 

Long.reverse(), Long.reverseBytes() 

Integer.reverse(), Integer.reverseBytes() 

Character.reverseBytes()

AArch64 C2 backend additionally can vectorize these operations using SVE: 

Long.numberOfTrailingZeros() 

Long.numberOfLeadingZeros() 

On platforms that support FEAT_SVE_BitPerm, JDK 21 also supports vectorization of: 

Integer.compress() 

Integer.expand() 

Long.compress() 

Long.expand() 

This extended vectorization support is expected to improve throughput of bit-oriented workloads across a wide range of AArch64 hardware. 

For example, 

void trailing_zeros(long[] la, long[] lb, int len) { 

    for (int i = 0; i < len; ++i) { 

        lb[i] = Long.numberOfTrailingZeros(la[i]); 

    } 

} 

JDK 17 can only generate scalar code for this loop: 

LOOP: 

  sbfiz  x12, x17, #3, #32 

  add    x1, x14, x12 

  ldr    x13, [x1,#16] 

  rbit   x13, x13 

  clz    x13, x13 

  sxtw   x13, w13 

  add    x2, x0, x12 

  str    x13, [x2,#16] 

  ... 

  add    w17, w17, #0x8  

  cmp    w17, w11 

  b.lt   LOOP 

Instead of computing trailing zeros one value at a time, JDK 21 uses SVE vector instructions to process multiple elements simultaneously, improving throughput for bit-processing workloads: 

LOOP: 

  sbfiz  x14, x12, #3, #32 

  add    x15, x18, x14 

  ldr    q16, [x15, #16] 

  rbit   z16.d, p7/m, z16.d 

  clz    z16.d, p7/m, z16.d 

  xtn    v16.2s, v16.2d 

  sxtl   v16.2d, v16.2s 

  add    x14, x16, x14 

  str    q16, [x14, #16] 

  ... 

  add    w12, w12, #0x10 

  cmp    w12, w13 

  b.lt   LOOP 

String.compareTo 

JDK 21 adds SVE implementation for String.compareTo, improving Latin-1 and UTF-16 comparisons for long strings. 

Example benchmark

Relative performance improvement for systems with different SVE vector lengths, where, higher values indicate better performance, based on the benchmark results reported in OpenJDK String.compareTo() microbenchmark: 

   case      size  128-bits  256-bits  512-bits 

compareToLL   24     0.17%     0.58%     0.00% 

compareToLL   36     0.00%    2.25%      0.04% 

compareToLL   72     -4.40%   3.87%      -12.82% 

compareToLL   128    4.55%    58.31%     13.53% 

compareToLL   256    19.39%   69.77%     82.03% 

compareToLL   512    1.81%    68.38%     170.93% 

compareToLU   24     25.57%   46.98%     54.61% 

compareToLU   36     36.03%   70.26%     94.33% 

compareToLU   72     35.86%   90.58%     146.04% 

compareToLU   128    70.82%   119.19%    266.22% 

compareToLU   256    80.77%   146.33%    420.01% 

compareToLU   512    94.62%   171.72%    530.87% 

compareToUL   24     20.82%   34.48%     62.14% 

compareToUL   36     39.77%   60.79%     69.77% 

compareToUL   72     35.46%   84.34%     121.90% 

compareToUL   128    67.77%   110.97%    220.53% 

compareToUL   256    77.05%   160.29%    331.30% 

compareToUL   512    91.88%   184.57%    524.21% 

compareToUU   24     -0.13%   0.40%      0.00% 

compareToUU   36     -9.18%   12.84%     -13.93% 

compareToUU   72     1.67%    60.61%     6.69% 

compareToUU   128    13.51%   60.33%     55.27% 

compareToUU   256    2.55%    62.17%     153.26% 

compareToUU   512    4.12%    68.62%     201.68% 

String.indexOf(char) 

Since JEP 254, “Compact Strings,” Java strings can use either UTF-16 or Latin-1 representations depending on their contents. 

JDK 21 introduces SVE intrinsics for String.indexOf(char) for both UTF-16 and Latin-1 strings. In JDK 17, the implementation compares 4 UTF-16 characters or 8 Latin-1 characters at a time. JDK 21 uses SVE to compare more characters in each iteration, improving search efficiency for longer strings. 

Example benchmark

This improves performance for benchmarks such as latin1_mixed_char() and utf16_mixed_char(), particularly for certain string lengths (for example, length = 65). 

Improvements in JDK 25 

Diagnostic flag for evaluating auto-vectorization profitability: AutoVectorizationOverrideProfitability 

The JDK HotSpot C2 compiler uses several heuristics to estimate whether vectorization is profitable, however, these heuristics are not always accurate. JDK 25 introduces the AutoVectorizationOverrideProfitability diagnostic flag, which gives developers more control over this behavior: 

   0: Disable vectorization, as if it were deemed unprofitable. 

    1: Use the existing profitability heuristics. 

    2: Always vectorize when possible, even if the heuristics predict it is unprofitable. 

This flag allows developers to override the default profitability checks. It can be used to assess performance  and evaluate the profitability heuristics. 

For a loop: 

int simple_reduction(int[] ia) { 

    int sum = 0; 

    for (int i = 0; i < ia.length; i++) { 

        sum += ia[i]; 

    } 

    return sum; 

} 

The existing heuristics do not consider simple reductions like the one above profitable to vectorize, resulting in assembly such as: 

LOOP: 

  add    x10, x20, w17, sxtw #2 

  ldp    w12, w13, [x10,#16] 

  add    w15, w19, w12 

  ldr    w12, [x10,#24] 

  add    w15, w15, w13 

  ldr    w13, [x10,#28] 

  add    w15, w15, w12 

  ldr    w12, [x10,#32] 

  add    w13, w15, w13 

  ldr    w1, [x10,#36] 

  add    w12, w13, w12 

  ldp    w18, w13, [x10,#40] 

  add    w10, w12, w1 

  add    w12, w10, w18 

  add    w17, w17, #0x8 

  add    w19, w12, w13 

  cmp    w17, w11 

  b.lt   LOOP  

We can force JDK 25 to vectorize this loop  by using -XX:+UnlockDiagnosticVMOptions -XX:AutoVectorizationOverrideProfitability=2  The resulting vectorized code is shown below. This lets us evaluate whether forcing vectorization improves performance for this simple reduction loop  and whether the current profitability heuristics make the right decision for this use case: 

LOOP: 

  mov    w13, w20 

  add    x15, x21, w13, sxtw #2 

  ldr    q18, [x15, #16] 

  ldr    q19, [x15, #32] 

  add    v17.4s, v18.4s, v17.4s 

  ldr    q18, [x15, #48] 

  add    v17.4s, v19.4s, v17.4s 

  ldr    q19, [x15, #64] 

  add    v17.4s, v18.4s, v17.4s 

  ldr    q18, [x15, #80] 

  add    v17.4s, v19.4s, v17.4s 

  ldr    q19, [x15, #96] 

  add    v17.4s, v18.4s, v17.4s 

  ldr    q18, [x15, #112] 

  add    v17.4s, v19.4s, v17.4s 

  ldr    q19, [x15, #128] 

  add    v17.4s, v18.4s, v17.4s 

  add    v17.4s, v19.4s, v17.4s 

  add    w20, w13, #0x20 

  cmp    w20, w10 

  b.lt   LOOP 

Read-forward loop 

For a loop like  

void read_forward(int[] ia, int[] ib, int len) { 

    for (int i = 0; i < len - 1; i++) { 

        ia[i] = ib[i] + ia[i + 1]; 

    } 

} 

This is a read-forward pattern, where each iteration reads a value from ia[i + 1] while writing to ia[i]. 

In JDK 21, C2 conservatively assumes a dependency between the vector load and store operations because they access partially overlapping memory regions. As a result, the compiler does not vectorize the loop. 

However, this overlap does not create a true dependency. The loaded values are consumed before the store updates memory, so vectorization preserves the original program semantics. JDK 25 improves the dependency analysis and correctly recognizes that no real dependency exists, allowing this read-forward pattern to be vectorized successfully. 

JDK 25 generates vector code such as: 

LOOP: 

  mov     w10, w14 

  sbfiz   x14, x10, #2, #32 

  add     x16, x11, x14 

  add     x14, x2, x14 

  ldur    q16, [x16, #20] 

  ldr     q17, [x14, #16] 

  add     v16.4s, v17.4s, v16.4s 

  str     q16, [x16, #16] 

  ... 

  add     w14, w10, #0x20 

  cmp     w14, w13 

  b.lt    LOOP 

Instead of the scalar code generated in JDK 21: 

LOOP: 

  sbfiz    x14, x13, #2, #32 

  add      x4, x19, x14 

  add      x3, x16, x14 

  ldr      w17, [x4, #16] 

  ldr      w14, [x3, #20] 

  add      w14, w14, w17 

  str      w14, [x3, #16] 

  ... 

  add      w13, w13, #0x8 

  cmp      w13, w2 

  b.lt     LOOP 

Split vectors for wider hardware 

For some operations, the AArch64 backend only supports specific vector lengths. In JDK 21, this can prevent loops from being vectorized on systems with larger vector sizes. 

For example: 

void mul_add_s2i(short[] sa, short[] sb, int[] ia, int len) { 

    for (int i = 0; i < len; i++) { 

        ia[i] += ((sa[2*i] * sb[2*i]) + (sa[2*i+1] * sb[2*i+1])); 

    } 

} 

The AArch64 backend in JDK 21 can vectorize this loop only when the operation uses16-byte vectors. On systems with a larger preferred vector length, for example, a 32-byte SVE vector length, C2 constructs a 32-byte vector operation. Because the backend does not support that vector width, the loop cannot be vectorized, and JDK 21 falls back to scalar code: 

LOOP: 

  lsl    w14, w15, #1 

  sbfiz  x13, x14, #1, #32 

  add    x20, x17, x13 

  add    x7, x16, x13 

  ldrsh  w1, [x20, #16] 

  ldrsh  w14, [x7, #18]  

  ldrsh  w13, [x20, #18]  

  add    x21, x19, w15, sxtw #2  

  ldrsh  w4, [x7, #16] 

  mul    w8, w13, w14 

  madd   w13, w1, w4, w8 

  ldr    w1, [x21, #16] 

  add    w1, w13, w1  

  str    w1, [x21, #16] 

  ... 

  add    w15, w15, #0x8 

  cmp    w15, w6 

  b.lt   LOOP  

However, in JDK 25, the compiler can split a 32-byte vector operation into 2 16-byte vector operations. This allows the loop to be vectorized successfully: 

LOOP: 

  mov w5, w2 

  lsl w12, w5, #1 

  sbfiz x12, x12, #1, #32 

  add x13, x15, x12 

  add x12, x3, x12 

  ldr q16, [x13, #16] 

  ldr q17, [x12, #16]  

  add x14, x19, w5, sxtw #2 

  ldr q18, [x12, #32] 

  ldr q19, [x13, #32] 

  ldr q20, [x14, #16] 

  smull v26.4s, v16.4h, v17.4h 

  smull2 v27.4s, v16.8h, v17.8h 

  addp v27.4s, v26.4s, v27.4s 

  add v16.4s, v27.4s, v20.4s 

  str q16, [x14, #16]  

  ... 

  add w2, w5, #0x10 

  cmp w2, w7 

  b.lt LOOP 

Unsafe.setMemory 

Unsafe::setMemory is used, for example, to zero newly allocated native MemorySegments. 

In JDK 21, Unsafe::setMemory  is implemented as a native method in unsafe.cpp. As a result, every invocation requires thread state transitions from Java to native and back to Java, which introduces noticeable overhead, especially for small memory fills. 

JDK 25 introduces a SIMD-based intrinsic for Unsafe::setMemory on AArch64. C2 intrinsifies this method as a leaf call, eliminating thread state transitions. The new implementation also uses vector instructions for memory writes. This reduces invocation overhead and improves performance, particularly for small fill sizes. 

Example benchmark

Arrays.hashCode() 

JDK 25 introduces intrinsics for Arrays.hashCode() on several primitive array types, including int[], char[], byte[], and short[]. 

NEON instructions are used to process multiple elements per iteration, improving performance of the method across all AArch64 systems. 

Example benchmark

Upgrade your JDK - benefit your workloads  

JDK 21 and JDK 25 extend the JVM’s ability to use AArch64 SIMD hardware through improved auto-vectorization and new intrinsic implementations. As OpenJDK evolves, more workloads can benefit from NEON and SVE acceleration without requiring application changes. 

Upgrading to a newer JDK release is one of the simplest ways to benefit from ongoing JVM performance improvements on modern Arm systems. 


1Log in to like this post
Share

Article text

Re-use is only permitted for informational and non-commercial or personal use only.

placeholder