# Native Code and JNI Optimization for Java

This guide covers JNI overhead, the Panama Foreign Function & Memory API, Unsafe operations and their modern replacements, GraalVM native-image, and the Vector API for SIMD operations.

## JNI Overhead

Java Native Interface (JNI) is the traditional mechanism for calling native code from Java. Every JNI call has fixed overhead that dominates for small, frequent operations.

### Call cost breakdown

| Operation | Approximate cost | Notes |
|-----------|-----------------|-------|
| JNI method call (Java -> native) | ~50-100 ns | Save/restore JNI frame, argument marshalling |
| JNI method call (native -> Java) | ~100-200 ns | Lookup method ID, construct arguments |
| `GetObjectField` / `SetObjectField` | ~10-20 ns | Access Java object fields from native code |
| `Get<Type>ArrayElements` (copy mode) | O(n) | Copies entire array to native memory |
| `Get<Type>ArrayElements` (pinned) | ~10 ns | Pins GC -- use `GetPrimitiveArrayCritical` |
| `GetStringUTFChars` | O(n) | Copies and converts to modified UTF-8 |
| `NewGlobalRef` / `DeleteGlobalRef` | ~20 ns | Reference management overhead |

### Critical regions

`GetPrimitiveArrayCritical` pins the array in the Java heap (prevents GC from moving it) and returns a direct pointer. This avoids copying but blocks GC for the duration.

```c
// Pin array -- GC is suspended for this thread
jint *elements = (*env)->GetPrimitiveArrayCritical(env, jarray, NULL);
// Do work with elements -- keep this section SHORT
memcpy(nativeBuf, elements, length * sizeof(jint));
(*env)->ReleasePrimitiveArrayCritical(env, jarray, elements, JNI_ABORT);
```

**Rules for critical regions**:
- Keep the critical section as short as possible (microseconds, not milliseconds)
- Do NOT call back into Java from a critical region
- Do NOT block or allocate Java objects in a critical region
- Do NOT nest critical regions
- GC pauses accumulate if many threads hold critical regions simultaneously

### Reducing JNI overhead

1. **Batch calls**: Instead of calling a native function per element, pass an array and process in bulk
2. **Cache method/field IDs**: `GetMethodID` and `GetFieldID` are expensive -- call once and cache
3. **Use direct ByteBuffer**: `ByteBuffer.allocateDirect()` provides a native memory pointer accessible from both Java and native code without copying
4. **Minimize reference management**: Every `NewGlobalRef` / `NewLocalRef` has a cost. Use local refs (auto-freed on native method return) when possible

## Panama: Foreign Function & Memory API (JDK 22+)

Panama replaces JNI with a pure-Java API for calling native functions and managing native memory. No native code compilation required.

### Key concepts

| Concept | Purpose | JNI equivalent |
|---------|---------|---------------|
| `MemorySegment` | Represents a contiguous block of native or heap memory | `GetPrimitiveArrayCritical` / `DirectByteBuffer` |
| `Arena` | Manages lifetime of memory segments (scoped allocation) | Manual `malloc/free` |
| `Linker` | Calls native functions from Java | JNI native method declarations + C implementation |
| `FunctionDescriptor` | Describes native function signature | JNI method signatures |
| `SymbolLookup` | Finds native function symbols in libraries | `System.loadLibrary` + native registration |

### Calling a native function

```java
// Look up the native strlen function
Linker linker = Linker.nativeLinker();
SymbolLookup stdlib = linker.defaultLookup();
MethodHandle strlen = linker.downcallHandle(
    stdlib.find("strlen").orElseThrow(),
    FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS)
);

// Call it
try (Arena arena = Arena.ofConfined()) {
    MemorySegment cString = arena.allocateFrom("Hello, Panama!");
    long len = (long) strlen.invoke(cString);
    // len == 14
}
// Memory automatically freed when arena closes
```

### Memory management with Arena

```java
// Confined arena: single-thread access, deterministic cleanup
try (Arena arena = Arena.ofConfined()) {
    MemorySegment buf = arena.allocate(1024);  // 1 KB native buffer
    buf.set(ValueLayout.JAVA_INT, 0, 42);
    int value = buf.get(ValueLayout.JAVA_INT, 0);  // 42
}
// buf is freed here -- no manual free() needed

// Shared arena: multi-thread access
try (Arena arena = Arena.ofShared()) {
    MemorySegment shared = arena.allocate(4096);
    // Multiple threads can access 'shared' concurrently
}

// Auto arena: GC-managed lifetime (like DirectByteBuffer)
Arena arena = Arena.ofAuto();
MemorySegment seg = arena.allocate(1024);
// Freed when seg is garbage collected -- no explicit close
```

### Performance comparison: JNI vs Panama

| Dimension | JNI | Panama |
|-----------|-----|--------|
| Call overhead | ~50-100 ns | ~10-30 ns (JIT can optimize downcalls) |
| Array access | Copy or pin (critical region) | Direct MemorySegment access (zero-copy) |
| Memory management | Manual (`malloc`/`free` in C, or GC-dependent `DirectByteBuffer`) | Arena-based (deterministic, composable) |
| Type safety | Minimal (C types, manual casts) | Full (Java type system, `ValueLayout` descriptors) |
| Build complexity | Requires C compiler, JNI header generation | Pure Java (no native compilation) |

### Migration from JNI to Panama

1. Identify JNI native methods with `native` keyword in Java source
2. For each native function: define `FunctionDescriptor`, create `MethodHandle` via `Linker`
3. Replace `GetPrimitiveArrayCritical` / `GetByteArrayElements` with `MemorySegment` operations
4. Replace manual `malloc`/`free` with `Arena` lifecycle management
5. Remove the native C/C++ source files and JNI header generation from the build

## Unsafe Operations

`sun.misc.Unsafe` provides direct memory access, CAS operations, and other low-level primitives. It is used extensively by JDK internals, Netty, Caffeine, and other high-performance libraries.

### Common uses and modern replacements

| Unsafe operation | Use case | Modern replacement |
|-----------------|----------|-------------------|
| `allocateMemory` / `freeMemory` | Off-heap allocation | `Arena.allocate()` (JDK 22+) or `ByteBuffer.allocateDirect()` |
| `getInt(address)` / `putInt(address, val)` | Direct memory access | `MemorySegment.get/set(ValueLayout.JAVA_INT, offset)` |
| `compareAndSwapInt` | Lock-free CAS | `VarHandle.compareAndSet()` (JDK 9+) |
| `getObjectVolatile` / `putObjectVolatile` | Volatile field access | `VarHandle.getVolatile()` / `VarHandle.setVolatile()` (JDK 9+) |
| `objectFieldOffset` | Field offset for CAS | `MethodHandles.lookup().findVarHandle()` |
| `allocateInstance` | Create instance without constructor | No direct replacement -- used by serialization frameworks |
| `park` / `unpark` | Thread scheduling | `LockSupport.park()` / `LockSupport.unpark()` (already the public API) |

### VarHandle for atomic operations

```java
// Define a VarHandle for a field
private static final VarHandle COUNT;
static {
    try {
        COUNT = MethodHandles.lookup()
            .findVarHandle(Counter.class, "count", int.class);
    } catch (ReflectiveOperationException e) {
        throw new ExceptionInInitializerError(e);
    }
}
private volatile int count;

// Atomic operations via VarHandle
public int increment() {
    return (int) COUNT.getAndAdd(this, 1);
}

public boolean compareAndSet(int expected, int update) {
    return COUNT.compareAndSet(this, expected, update);
}
```

## GraalVM Native Image

Ahead-of-time compilation of Java applications to native executables. Eliminates JVM startup, class loading, and JIT compilation.

### Performance characteristics

| Dimension | JVM (HotSpot) | Native image |
|-----------|--------------|-------------|
| Startup time | 500 ms - 5 s | 10 - 50 ms |
| Peak throughput | Higher (JIT optimizes hot paths with profile data) | Lower (~10-30% less for long-running workloads) |
| Memory footprint | Higher (JIT compiler, interpreter, class metadata) | Lower (no JIT, no interpreter, pre-resolved metadata) |
| Warmup period | 10-60 s to reach peak performance | None (already compiled) |
| Build time | Milliseconds (javac) | Minutes (static analysis + compilation) |

### Reflection configuration

Native image performs closed-world analysis at build time. Reflective access must be declared:

```json
// reflect-config.json
[
  {
    "name": "com.example.model.User",
    "allDeclaredConstructors": true,
    "allPublicMethods": true,
    "allDeclaredFields": true
  }
]
```

Or use the tracing agent to auto-generate configuration:

```bash
java -agentlib:native-image-agent=config-output-dir=/tmp/native-config -jar app.jar
# Run the application through all code paths
# Configuration files are written to /tmp/native-config/
```

### When to use native image

- **CLI tools**: Instant startup is critical for user experience
- **Serverless / Lambda**: Cold start time directly affects cost and latency
- **Microservices with strict startup SLA**: Container orchestrators expect fast readiness
- **Memory-constrained environments**: Lower baseline memory footprint

### When to avoid native image

- **Long-running servers optimized for throughput**: JIT's profile-guided optimization produces faster code for hot paths
- **Heavy use of reflection/dynamic proxies**: Configuration burden is high and easy to get wrong
- **Rapid development cycle**: Build times of 5-15 minutes slow iteration

## SIMD via Vector API (JDK 22+ Incubator)

The Vector API allows explicit SIMD (Single Instruction, Multiple Data) operations from Java. The JIT compiler translates Vector API calls to CPU SIMD instructions (SSE, AVX2, AVX-512, NEON).

### Key concepts

```java
import jdk.incubator.vector.*;

// Species: defines vector shape (element type + lane count)
static final VectorSpecies<Float> SPECIES = FloatVector.SPECIES_PREFERRED;
// SPECIES_PREFERRED uses the widest SIMD width available on the current CPU
// e.g., 256-bit AVX2 -> 8 float lanes, 512-bit AVX-512 -> 16 float lanes

// Vectorized array addition
public static float[] add(float[] a, float[] b) {
    float[] result = new float[a.length];
    int i = 0;
    int upperBound = SPECIES.loopBound(a.length);

    // Vectorized loop: processes SPECIES.length() elements per iteration
    for (; i < upperBound; i += SPECIES.length()) {
        FloatVector va = FloatVector.fromArray(SPECIES, a, i);
        FloatVector vb = FloatVector.fromArray(SPECIES, b, i);
        va.add(vb).intoArray(result, i);
    }

    // Scalar tail: handles remaining elements
    for (; i < a.length; i++) {
        result[i] = a[i] + b[i];
    }
    return result;
}
```

### Masks for conditional operations

```java
// Vectorized conditional: only process elements where mask is true
VectorMask<Float> mask = va.compare(VectorOperators.GT, 0.0f);
FloatVector result = va.mul(2.0f, mask);  // multiply by 2 only where a > 0
```

### Common operations

| Operation | Method | Notes |
|-----------|--------|-------|
| Element-wise arithmetic | `add`, `sub`, `mul`, `div` | All return new vectors |
| Reduction | `reduceLanes(VectorOperators.ADD)` | Sum all lanes to scalar |
| Comparison | `compare(VectorOperators.GT, value)` | Returns `VectorMask` |
| Blend | `blend(other, mask)` | Select elements from two vectors based on mask |
| Rearrange | `rearrange(shuffle)` | Permute lanes |
| Gather/Scatter | `fromArray` with index array | Indexed load/store |

### When the Vector API helps

- **Array-heavy computation**: Image processing, signal processing, scientific computing
- **Operations the JIT auto-vectorizer misses**: Complex conditionals, gather/scatter patterns, multi-array operations
- **Predictable SIMD usage**: When you know the data layout and can design for vector-width-aligned processing

### When to rely on JIT auto-vectorization instead

The JIT compiler already auto-vectorizes simple patterns (array copy, element-wise arithmetic on primitive arrays). The Vector API is for cases where:
- Auto-vectorization fails (complex control flow, data-dependent branches)
- You need guaranteed vectorization (auto-vectorization is best-effort)
- You need specific SIMD operations (masked operations, shuffles, reductions)

## Pitfalls

- **JNI pinning blocks GC**: `GetPrimitiveArrayCritical` blocks garbage collection on ALL threads for the duration of the critical region. Keep critical regions under 1 microsecond.
- **Panama requires JDK 22+**: The Foreign Function & Memory API is finalized in JDK 22. Earlier JDK versions have it as preview/incubator with potentially different APIs.
- **Unsafe removal**: `sun.misc.Unsafe` is scheduled for removal. Migrate to `VarHandle` (atomic ops) and `MemorySegment` (memory access) proactively.
- **Native image reflection gaps**: Missing reflection configuration causes `ClassNotFoundException` or `NoSuchMethodException` at runtime -- not at build time. Always run the tracing agent against realistic workloads.
- **Vector API is incubating**: The API may change between JDK versions. It requires `--add-modules jdk.incubator.vector` at compile and runtime.
- **SIMD alignment**: For maximum throughput, align arrays to cache line boundaries (64 bytes). Misaligned access works but may be slower on some architectures.
