Low-level Java programming: JNI, off-heap memory, performance optimization, and profiling.
| Class | Topic |
|---|---|
JniExample |
JNI native method declarations, library loading, Java fallback pattern |
OffHeapMemoryExample |
Direct ByteBuffer, off-heap arrays with Cleaner, memory-mapped files |
PerformanceBenchmarks |
JMH benchmarks: string concat, boxed vs primitive, collections, streams |
ProfilingUtils |
JVM snapshots via MXBeans, execution timing, GC monitoring |
JNI lets Java call native C/C++ code. The workflow:
1. Declare native methods in Java
2. Generate C header: javac -h native/include src/.../JniExample.java
3. Implement C functions matching generated signatures
4. Compile shared library:
Linux: gcc -shared -fPIC -o libnative_example.so -I$JAVA_HOME/include -I$JAVA_HOME/include/linux native.c
macOS: gcc -shared -o libnative_example.dylib -I$JAVA_HOME/include -I$JAVA_HOME/include/darwin native.c
5. Run with: java -Djava.library.path=./native com.example.template.systems.JniExample
The JniExample class also demonstrates a fallback pattern — try native, fall back to pure Java — useful for optional native acceleration.
Direct ByteBuffer allocates memory outside the GC heap:
ByteBuffer buf = ByteBuffer.allocateDirect(1024).order(ByteOrder.nativeOrder());
buf.putInt(42);
buf.flip();
int val = buf.getInt(); // 42Use cases:
- Large buffers that would cause GC pressure
- Zero-copy I/O (NIO channels)
- Interop with native code via JNI
- Memory-mapped files (
FileChannel.map())
The OffHeapDoubleArray class shows the Cleaner pattern (Java 9+) for deterministic resource cleanup.
Java 21+: The Foreign Function & Memory API (
java.lang.foreign) provides a safer, more ergonomic alternative to both JNI and direct ByteBuffers. Consider migrating when targeting Java 21+.
mvn -pl examples/systems compile exec:java \
-Dexec.mainClass="com.example.template.systems.PerformanceBenchmarks"| Benchmark | Relative Speed | Why |
|---|---|---|
StringBuilder vs String += |
~50-100x faster | Avoids O(n²) copying |
long vs Long sum |
~3-5x faster | No auto-boxing allocation |
ArrayList vs LinkedList |
~2-3x faster | Cache-friendly contiguous memory |
for loop vs Stream |
~similar | JIT optimizes both; streams add small overhead |
- Measure first — use JMH, JFR, or async-profiler before optimizing
- Avoid premature optimization — focus on algorithmic complexity first
- Minimize allocations in hot paths (reuse objects, use primitives)
- Right-size collections —
new ArrayList<>(expectedSize),new HashMap<>(cap, 0.75f) - Prefer
StringBuilderfor string building in loops - Use primitives over boxed types in computation-heavy code
- Keep hot methods small — helps JIT inlining (< ~325 bytecodes)
- Avoid megamorphic calls — limit interface implementations at hot call sites to ≤2
# Start recording with the application
java -XX:StartFlightRecording=filename=app.jfr,duration=60s,settings=profile -jar app.jar
# Attach to running process
jcmd <pid> JFR.start filename=app.jfr duration=60s
# Analyze with JDK Mission Control (JMC)
jmc app.jfr# CPU flame graph
./asprof -d 30 -f cpu.html <pid>
# Allocation profiling
./asprof -e alloc -d 30 -f alloc.html <pid>
# Wall-clock (includes I/O wait)
./asprof -e wall -d 30 -f wall.html <pid>ProfilingUtils.profileTask("My Operation", () -> {
// code to profile
});
// Prints: heap usage, GC count/time, elapsed time-Xlog:gc*:file=gc.log # GC logging (Java 9+)
-XX:NativeMemoryTracking=summary # Native memory tracking
-XX:+HeapDumpOnOutOfMemoryError # Auto heap dump on OOM
-XX:+PrintCompilation # JIT compilation log
-XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining # Inlining decisions
# Compile
mvn -pl examples/systems compile
# Run tests
mvn -pl examples/systems test
# Run benchmarks
mvn -pl examples/systems compile exec:java \
-Dexec.mainClass="com.example.template.systems.PerformanceBenchmarks"- Main README — Project overview and quick start
- Best Practices — Performance, code style
- Development Workflow — Profiling guidance
- HPC Module — Parallel streams, virtual threads
- Toolchain — JDK and build tool setup