On-Stack Replacement (OSR) is a crucial JVM optimization technique that allows replacing currently executing code with optimized versions while the code is running. This article provides a comprehensive exploration of OSR in Java, including implementation details, use cases, and practical examples.
Understanding OSR Fundamentals
Step 1: Core Concepts and Problem Statement
package com.example.osr;
public class OSRCoreConcepts {
// Problem: Long-running methods may never get optimized
public static long sumPrimitiveLoop(int n) {
long sum = 0;
for (int i = 0; i < n; i++) {
sum += i;
// Without OSR, this loop would run in interpreted mode
// until the method completes and becomes "hot"
}
return sum;
}
// Hot loop that benefits from OSR
public static void processLargeArray(int[] data, int threshold) {
int operations = 0;
for (int i = 0; i < data.length; i++) {
// This loop body becomes hot quickly
if (data[i] > threshold) {
data[i] = transformValue(data[i]);
operations++;
}
// Without OSR, we'd wait for method completion to optimize
// With OSR, the loop can be optimized while running
}
System.out.println("Operations performed: " + operations);
}
private static int transformValue(int value) {
return value * 2 + 1;
}
// Method that demonstrates OSR triggers
public static void demonstrateOSRScenario() {
// Phase 1: Interpretation
System.out.println("Starting in interpreted mode...");
// Phase 2: OSR kicks in for hot loop
long result = 0;
for (int i = 0; i < 1000000; i++) {
result += complexCalculation(i);
// After certain iterations, JVM detects this as hot code
// and performs OSR to replace with compiled version
}
System.out.println("Result: " + result);
}
private static long complexCalculation(int n) {
// Simulate some computation
return (long) n * n + 2 * n + 1;
}
}
JVM Internals and OSR Mechanics
Step 2: Understanding JVM OSR Implementation
package com.example.osr.jvm;
// Simplified representation of JVM OSR mechanics
public class OSRMechanics {
public static class MethodState {
private final String methodName;
private int invocationCount;
private int backedgeCount; // Loop back-edge counter
private boolean compiled;
private CompilationLevel compilationLevel;
public MethodState(String methodName) {
this.methodName = methodName;
this.invocationCount = 0;
this.backedgeCount = 0;
this.compiled = false;
this.compilationLevel = CompilationLevel.INTERPRETED;
}
public void recordInvocation() {
invocationCount++;
checkForCompilation();
}
public void recordBackedge() {
backedgeCount++;
checkForOSR();
}
private void checkForCompilation() {
// Tiered compilation thresholds
if (!compiled && invocationCount >= 1000) { // C1 threshold
triggerCompilation(CompilationLevel.SIMPLE);
}
if (invocationCount >= 10000) { // C2 threshold
triggerCompilation(CompilationLevel.FULL);
}
}
private void checkForOSR() {
// OSR threshold based on backedges
if (!compiled && backedgeCount >= 10000) {
triggerOSRCompilation();
}
}
private void triggerCompilation(CompilationLevel level) {
System.out.println("Compiling method: " + methodName + " at level: " + level);
this.compilationLevel = level;
this.compiled = true;
}
private void triggerOSRCompilation() {
System.out.println("Triggering OSR for method: " + methodName);
this.compilationLevel = CompilationLevel.OSR;
this.compiled = true;
}
// Getters
public boolean isCompiled() { return compiled; }
public CompilationLevel getCompilationLevel() { return compilationLevel; }
}
public enum CompilationLevel {
INTERPRETED, // Pure interpretation
OSR, // On-Stack Replacement compiled
SIMPLE, // C1 compiled
FULL // C2 compiled
}
// Frame representation for OSR
public static class StackFrame {
private final String methodName;
private final int bci; // Bytecode index
private final Object[] locals;
private final Object[] stack;
private final Object lockObject; // For synchronized methods
public StackFrame(String methodName, int bci, Object[] locals,
Object[] stack, Object lockObject) {
this.methodName = methodName;
this.bci = bci;
this.locals = locals;
this.stack = stack;
this.lockObject = lockObject;
}
// OSR transition point
public OSRTransition prepareOSRTransition() {
return new OSRTransition(this);
}
// Getters
public String getMethodName() { return methodName; }
public int getBci() { return bci; }
public Object[] getLocals() { return locals; }
public Object[] getStack() { return stack; }
}
public static class OSRTransition {
private final StackFrame oldFrame;
private StackFrame newFrame;
private boolean transitionReady;
public OSRTransition(StackFrame oldFrame) {
this.oldFrame = oldFrame;
this.transitionReady = false;
}
public void prepareCompiledFrame(MethodState newMethodState) {
// Convert interpreted frame to compiled frame format
this.newFrame = transformFrameForCompiledCode(oldFrame);
this.transitionReady = true;
}
public void executeTransition() {
if (transitionReady && newFrame != null) {
System.out.println("Executing OSR transition from interpreted to compiled code");
// Actual transition happens here in JVM
swapFrames(oldFrame, newFrame);
}
}
private StackFrame transformFrameForCompiledCode(StackFrame interpretedFrame) {
// Convert interpreted frame layout to compiled code layout
// This involves:
// 1. Mapping local variables
// 2. Converting stack values
// 3. Handling monitor state
return new StackFrame(
interpretedFrame.getMethodName(),
interpretedFrame.getBci(),
interpretedFrame.getLocals(),
interpretedFrame.getStack(),
interpretedFrame.lockObject
);
}
private void swapFrames(StackFrame oldFrame, StackFrame newFrame) {
// In actual JVM, this would involve:
// 1. Patching return address
// 2. Updating frame pointers
// 3. Transferring state
System.out.println("Frame swapped successfully");
}
}
}
OSR in HotSpot JVM
Step 3: HotSpot-specific OSR Details
package com.example.osr.hotspot;
import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
import java.util.List;
public class HotSpotOSR {
// JVM flags related to OSR
public static class OSRFlags {
public static final String COMPILE_THRESHOLD = "CompileThreshold";
public static final String TIERED_COMPILATION = "TieredCompilation";
public static final String OSR_COMPILATION = "OnStackReplace";
public static final String BACKEDGE_THRESHOLD = "BackEdgeThreshold";
public static void printOSRRelatedFlags() {
RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean();
List<String> arguments = runtimeMxBean.getInputArguments();
System.out.println("OSR-related JVM flags:");
for (String arg : arguments) {
if (arg.contains("Compile") ||
arg.contains("OSR") ||
arg.contains("BackEdge") ||
arg.contains("Tiered")) {
System.out.println(" " + arg);
}
}
}
}
// Methods that demonstrate OSR behavior
public static class OSRDemonstrator {
// This method will trigger OSR due to long-running loop
public static long hotLoopOSR(int iterations) {
long result = 0;
// Phase 1: Interpreted execution
System.out.println("Starting loop in interpreted mode...");
for (int i = 0; i < iterations; i++) {
result += compute(i);
// OSR trigger point - after many backedges
if (i == 10000) {
System.out.println("OSR likely triggered around iteration 10000");
}
}
return result;
}
// Nested loops - multiple OSR opportunities
public static void nestedLoopOSR(int outer, int inner) {
int total = 0;
for (int i = 0; i < outer; i++) {
// Outer loop might get OSR
for (int j = 0; j < inner; j++) {
// Inner loop is more likely to get OSR first
total += i * j;
// Complex computation to make loop "hot"
if (total % 1000 == 0) {
total = manipulate(total);
}
}
}
System.out.println("Nested loop result: " + total);
}
private static int manipulate(int value) {
return (value * 31) ^ 0xDEADBEEF;
}
private static long compute(int n) {
// Make computation non-trivial
return (long) Math.sqrt(n) * n + n % 7;
}
// Method with multiple hot spots
public static void multipleHotSpots(int size) {
int[] data = new int[size];
// First hot spot
for (int i = 0; i < size; i++) {
data[i] = i * 2;
}
// Second hot spot
long sum = 0;
for (int i = 0; i < size; i++) {
if (data[i] > 1000) {
sum += data[i];
}
}
// Third hot spot
for (int i = 0; i < size; i++) {
data[i] = transform(data[i]);
}
System.out.println("Multiple hot spots processed, sum: " + sum);
}
private static int transform(int value) {
return value ^ (value >>> 16);
}
}
// OSR state monitoring
public static class OSRMonitor {
private static long osrTransitionCount = 0;
private static long compilationCount = 0;
public static void recordOSRTransition() {
osrTransitionCount++;
System.out.println("OSR transition recorded. Total: " + osrTransitionCount);
}
public static void recordCompilation() {
compilationCount++;
}
public static void printStatistics() {
System.out.println("OSR Statistics:");
System.out.println(" Total OSR transitions: " + osrTransitionCount);
System.out.println(" Total compilations: " + compilationCount);
}
}
}
Tiered Compilation and OSR
Step 4: Tiered Compilation Integration
package com.example.osr.tiered;
import java.util.concurrent.atomic.AtomicLong;
public class TieredCompilationOSR {
// Tiered compilation levels
public enum CompilationTier {
INTERPRETED(0), // Pure interpretation
OSR_SIMPLE(1), // OSR with C1 compiler
OSR_FULL(2), // OSR with C2 compiler
SIMPLE(3), // C1 compiled
FULL(4); // C2 compiled (highest optimization)
private final int level;
CompilationTier(int level) {
this.level = level;
}
public int getLevel() {
return level;
}
public boolean isBetterThan(CompilationTier other) {
return this.level > other.level;
}
}
// Method compilation state
public static class MethodCompilationState {
private final String methodName;
private volatile CompilationTier currentTier;
private final AtomicLong invocationCount;
private final AtomicLong backedgeCount;
private volatile boolean osrStubInstalled;
public MethodCompilationState(String methodName) {
this.methodName = methodName;
this.currentTier = CompilationTier.INTERPRETED;
this.invocationCount = new AtomicLong(0);
this.backedgeCount = new AtomicLong(0);
this.osrStubInstalled = false;
}
public void recordInvocation() {
long count = invocationCount.incrementAndGet();
checkForTierPromotion(count);
}
public void recordBackedge() {
long count = backedgeCount.incrementAndGet();
checkForOSR(count);
}
private void checkForTierPromotion(long invocationCount) {
// Tiered compilation thresholds
if (currentTier == CompilationTier.INTERPRETED && invocationCount >= 1000) {
promoteToTier(CompilationTier.SIMPLE);
} else if (currentTier == CompilationTier.SIMPLE && invocationCount >= 10000) {
promoteToTier(CompilationTier.FULL);
}
}
private void checkForOSR(long backedgeCount) {
// OSR thresholds
if (!osrStubInstalled && backedgeCount >= 5000) {
installOSRStub();
}
// Upgrade OSR tier
if (osrStubInstalled && backedgeCount >= 20000 &&
currentTier == CompilationTier.OSR_SIMPLE) {
promoteToTier(CompilationTier.OSR_FULL);
}
}
private void promoteToTier(CompilationTier newTier) {
if (newTier.isBetterThan(currentTier)) {
System.out.println("Promoting " + methodName + " from " +
currentTier + " to " + newTier);
currentTier = newTier;
}
}
private void installOSRStub() {
System.out.println("Installing OSR stub for: " + methodName);
osrStubInstalled = true;
if (currentTier == CompilationTier.INTERPRETED) {
currentTier = CompilationTier.OSR_SIMPLE;
}
}
// Getters
public CompilationTier getCurrentTier() { return currentTier; }
public long getInvocationCount() { return invocationCount.get(); }
public long getBackedgeCount() { return backedgeCount.get(); }
public boolean isOsrStubInstalled() { return osrStubInstalled; }
}
// Demonstration of tiered compilation with OSR
public static class TieredOSRDemo {
public static void runTieredDemo() {
MethodCompilationState methodState = new MethodCompilationState("tieredDemo");
// Simulate method execution with increasing intensity
for (int phase = 1; phase <= 5; phase++) {
System.out.println("\n--- Phase " + phase + " ---");
simulateMethodExecution(methodState, phase * 5000);
printState(methodState);
}
}
private static void simulateMethodExecution(MethodCompilationState state, int iterations) {
for (int i = 0; i < iterations; i++) {
state.recordInvocation();
// Simulate loop backedge every few invocations
if (i % 10 == 0) {
state.recordBackedge();
}
}
}
private static void printState(MethodCompilationState state) {
System.out.println("Method: " + state.getCurrentTier() +
" | Invocations: " + state.getInvocationCount() +
" | Backedges: " + state.getBackedgeCount() +
" | OSR: " + state.isOsrStubInstalled());
}
// Real-world example: Data processing pipeline
public static void processDataWithTieredCompilation(double[] data) {
MethodCompilationState processState = new MethodCompilationState("processData");
// Phase 1: Initial processing (interpreted)
normalizeData(data, processState);
// Phase 2: Heavy computation (likely OSR)
applyTransformations(data, processState);
// Phase 3: Final aggregation (compiled)
aggregateResults(data, processState);
}
private static void normalizeData(double[] data, MethodCompilationState state) {
for (int i = 0; i < data.length; i++) {
state.recordInvocation();
if (i > 0) state.recordBackedge();
data[i] = data[i] / 255.0; // Normalize to [0,1]
}
}
private static void applyTransformations(double[] data, MethodCompilationState state) {
for (int i = 0; i < data.length; i++) {
state.recordInvocation();
if (i > 0) state.recordBackedge();
// Multiple transformations that benefit from optimization
data[i] = Math.log1p(data[i]);
data[i] = Math.sin(data[i] * Math.PI);
data[i] = data[i] * data[i];
}
}
private static void aggregateResults(double[] data, MethodCompilationState state) {
double sum = 0;
for (int i = 0; i < data.length; i++) {
state.recordInvocation();
if (i > 0) state.recordBackedge();
sum += data[i];
}
System.out.println("Final sum: " + sum);
}
}
}
OSR in Modern Java Features
Step 5: OSR with Streams and Lambdas
package com.example.osr.modern;
import java.util.*;
import java.util.stream.*;
import java.util.concurrent.*;
public class ModernJavaOSR {
// OSR with Java Streams
public static class StreamOSR {
// Stream operations that trigger OSR
public static long streamProcessingOSR(List<Integer> numbers) {
return numbers.stream()
.filter(n -> n % 2 == 0) // Lambda might get OSR
.mapToLong(n -> (long) n * n) // Another lambda
.sum(); // Terminal operation
}
// Complex stream pipeline
public static double complexStreamOSR(List<Double> values) {
return values.parallelStream() // Parallel might have different OSR behavior
.map(x -> Math.log(x + 1.0)) // Hot lambda
.filter(x -> x > 0.0)
.map(x -> x * x)
.reduce(0.0, Double::sum);
}
// Stream with stateful operations
public static List<Integer> statefulStreamOSR(int[] data) {
return Arrays.stream(data)
.boxed()
.sorted() // Stateful - might affect OSR
.distinct() // Another stateful operation
.collect(Collectors.toList());
}
}
// OSR with CompletableFuture and async operations
public static class AsyncOSR {
public static CompletableFuture<Long> asyncComputationOSR(int n) {
return CompletableFuture.supplyAsync(() -> {
// This lambda runs in ForkJoinPool and can benefit from OSR
long result = 0;
for (int i = 0; i < n; i++) {
result += computeAsync(i);
}
return result;
});
}
private static long computeAsync(int value) {
// Simulate async computation
return ThreadLocalRandom.current().nextLong(100) + value;
}
// Multiple async stages with OSR
public static CompletableFuture<Void> pipelineOSR(List<String> data) {
return CompletableFuture.supplyAsync(() -> processBatch(data))
.thenApplyAsync(ModernJavaOSR::transformBatch)
.thenAcceptAsync(ModernJavaOSR::storeResults)
.exceptionally(throwable -> {
System.err.println("Pipeline failed: " + throwable.getMessage());
return null;
});
}
private static List<String> processBatch(List<String> data) {
return data.stream()
.filter(s -> !s.isEmpty())
.map(String::toUpperCase)
.collect(Collectors.toList());
}
}
// OSR with virtual threads (Project Loom)
public static class VirtualThreadOSR {
public static void virtualThreadDemo() throws Exception {
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
List<Future<Long>> futures = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
final int taskId = i;
Future<Long> future = executor.submit(() -> {
// Each virtual thread might trigger OSR independently
return computeWithOSR(taskId);
});
futures.add(future);
}
long total = 0;
for (Future<Long> future : futures) {
total += future.get();
}
System.out.println("Virtual threads total: " + total);
}
}
private static long computeWithOSR(int base) {
long result = 0;
// This loop in each virtual thread can trigger OSR independently
for (int i = 0; i < 10000; i++) {
result += base * i + complexFunction(i);
}
return result;
}
private static long complexFunction(int n) {
return (long) Math.pow(n % 100, 2);
}
}
// Helper methods
private static List<String> transformBatch(List<String> batch) {
return batch.stream()
.map(s -> "processed_" + s)
.collect(Collectors.toList());
}
private static void storeResults(List<String> results) {
// Simulate storage operation
System.out.println("Storing " + results.size() + " results");
}
}
OSR Performance Impact Analysis
Step 6: Measuring OSR Effects
package com.example.osr.performance;
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
public class OSRPerformanceAnalysis {
// Performance counters for OSR analysis
public static class OSRPerformanceCounters {
private final AtomicLong interpretedTime = new AtomicLong(0);
private final AtomicLong osrTime = new AtomicLong(0);
private final AtomicLong compiledTime = new AtomicLong(0);
private final AtomicLong transitionCount = new AtomicLong(0);
public void recordInterpretedTime(long nanos) {
interpretedTime.addAndGet(nanos);
}
public void recordOSRTime(long nanos) {
osrTime.addAndGet(nanos);
}
public void recordCompiledTime(long nanos) {
compiledTime.addAndGet(nanos);
}
public void recordTransition() {
transitionCount.incrementAndGet();
}
public void printAnalysis() {
long totalTime = interpretedTime.get() + osrTime.get() + compiledTime.get();
System.out.println("OSR Performance Analysis:");
System.out.printf("Interpreted time: %d ns (%.1f%%)%n",
interpretedTime.get(), (interpretedTime.get() * 100.0 / totalTime));
System.out.printf("OSR time: %d ns (%.1f%%)%n",
osrTime.get(), (osrTime.get() * 100.0 / totalTime));
System.out.printf("Compiled time: %d ns (%.1f%%)%n",
compiledTime.get(), (compiledTime.get() * 100.0 / totalTime));
System.out.printf("Total transitions: %d%n", transitionCount.get());
}
}
// Benchmark to measure OSR impact
public static class OSRBenchmark {
public static void runOSRBenchmark() {
OSRPerformanceCounters counters = new OSRPerformanceCounters();
// Warm up to trigger compilation
System.out.println("Warming up...");
for (int i = 0; i < 5; i++) {
benchmarkMethod(1000, counters);
}
// Actual benchmark
System.out.println("Running benchmark...");
long startTime = System.nanoTime();
for (int i = 0; i < 10; i++) {
benchmarkMethod(1000000, counters);
}
long totalTime = System.nanoTime() - startTime;
System.out.printf("Total benchmark time: %.3f ms%n", totalTime / 1_000_000.0);
counters.printAnalysis();
}
private static long benchmarkMethod(int iterations, OSRPerformanceCounters counters) {
long result = 0;
long phaseStart = System.nanoTime();
// Phase 1: Likely interpreted
for (int i = 0; i < Math.min(iterations, 1000); i++) {
result += compute(i);
}
counters.recordInterpretedTime(System.nanoTime() - phaseStart);
// Phase 2: OSR likely triggers here
phaseStart = System.nanoTime();
for (int i = 1000; i < iterations; i++) {
result += compute(i);
// Simulate OSR transition point
if (i == 5000) {
counters.recordTransition();
counters.recordOSRTime(System.nanoTime() - phaseStart);
phaseStart = System.nanoTime();
}
}
if (iterations > 5000) {
counters.recordCompiledTime(System.nanoTime() - phaseStart);
}
return result;
}
private static long compute(int n) {
// Non-trivial computation
return (long) (Math.sin(n) * Math.cos(n) * 1000);
}
}
// Comparing with and without OSR potential
public static class OSRComparison {
public static void compareOSRvsNonOSR() {
int size = 1000000;
long startTime = System.nanoTime();
long result1 = methodWithOSROpportunity(size);
long time1 = System.nanoTime() - startTime;
startTime = System.nanoTime();
long result2 = methodWithoutOSROpportunity(size);
long time2 = System.nanoTime() - startTime;
System.out.println("OSR vs Non-OSR Comparison:");
System.out.printf("With OSR opportunity: %d ns, result: %d%n", time1, result1);
System.out.printf("Without OSR opportunity: %d ns, result: %d%n", time2, result2);
System.out.printf("Speedup: %.2fx%n", (double) time2 / time1);
}
// Method that allows OSR (long-running loop)
private static long methodWithOSROpportunity(int n) {
long sum = 0;
for (int i = 0; i < n; i++) {
sum += i * i - i + 1;
}
return sum;
}
// Method that prevents OSR (short method calls)
private static long methodWithoutOSROpportunity(int n) {
long total = 0;
int chunkSize = 1000;
int chunks = n / chunkSize;
for (int chunk = 0; chunk < chunks; chunk++) {
total += processChunk(chunk * chunkSize, Math.min((chunk + 1) * chunkSize, n));
}
return total;
}
private static long processChunk(int start, int end) {
long chunkSum = 0;
for (int i = start; i < end; i++) {
chunkSum += i * i - i + 1;
}
return chunkSum;
}
}
}
OSR in Real-World Applications
Step 7: Practical OSR Use Cases
package com.example.osr.practical;
import java.util.*;
import java.util.concurrent.*;
import java.util.stream.*;
public class PracticalOSRUseCases {
// 1. Scientific Computing
public static class ScientificComputing {
// Numerical integration with OSR
public static double integrate(Function<Double, Double> function,
double a, double b, int steps) {
double sum = 0.0;
double dx = (b - a) / steps;
for (int i = 0; i < steps; i++) {
double x = a + i * dx;
sum += function.apply(x) * dx;
// This loop becomes hot quickly for large steps
if (i % 10000 == 0 && i > 0) {
System.out.println("Progress: " + (i * 100.0 / steps) + "%");
}
}
return sum;
}
// Matrix multiplication with OSR
public static double[][] matrixMultiply(double[][] a, double[][] b) {
int n = a.length;
int m = b[0].length;
int p = b.length;
double[][] result = new double[n][m];
// Triple nested loop - prime candidate for OSR
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
double sum = 0.0;
for (int k = 0; k < p; k++) {
sum += a[i][k] * b[k][j];
}
result[i][j] = sum;
}
}
return result;
}
}
// 2. Data Processing
public static class DataProcessing {
// Large dataset processing with OSR
public static Map<String, Integer> processLogFiles(List<String> logs,
Set<String> keywords) {
Map<String, Integer> keywordCounts = new ConcurrentHashMap<>();
logs.parallelStream().forEach(log -> {
// Each parallel task can benefit from OSR independently
processSingleLog(log, keywords, keywordCounts);
});
return keywordCounts;
}
private static void processSingleLog(String log, Set<String> keywords,
Map<String, Integer> counts) {
String[] words = log.toLowerCase().split("\\W+");
for (String word : words) {
if (keywords.contains(word)) {
counts.merge(word, 1, Integer::sum);
}
}
}
// Real-time data stream processing
public static class StreamingProcessor {
private final BlockingQueue<DataPoint> queue;
private volatile boolean running;
private long processedCount;
public StreamingProcessor() {
this.queue = new LinkedBlockingQueue<>(10000);
this.running = true;
this.processedCount = 0;
}
public void startProcessing() {
Thread processorThread = new Thread(this::processLoop);
processorThread.setDaemon(true);
processorThread.start();
}
private void processLoop() {
// This long-running loop benefits greatly from OSR
while (running) {
try {
DataPoint point = queue.poll(100, TimeUnit.MILLISECONDS);
if (point != null) {
processDataPoint(point);
processedCount++;
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
private void processDataPoint(DataPoint point) {
// Complex processing that becomes optimized via OSR
double value = point.getValue();
for (int i = 0; i < 100; i++) {
value = Math.sin(value) * Math.cos(value) + 1.0;
}
point.setProcessedValue(value);
}
public void submitData(DataPoint point) {
queue.offer(point);
}
public void stop() {
running = false;
}
public long getProcessedCount() {
return processedCount;
}
}
}
// 3. Game Development
public static class GameEngine {
// Game loop with OSR optimization
public static class GameLoop {
private static final int TPS = 60; // Ticks per second
private static final long NANOS_PER_TICK = 1_000_000_000 / TPS;
private volatile boolean running;
private long tickCount;
public void start() {
running = true;
tickCount = 0;
long lastTime = System.nanoTime();
double delta = 0;
// Main game loop - prime candidate for OSR
while (running) {
long currentTime = System.nanoTime();
delta += (currentTime - lastTime) / (double) NANOS_PER_TICK;
lastTime = currentTime;
while (delta >= 1) {
tick();
delta--;
}
render(delta);
// Yield to prevent busy-waiting
Thread.yield();
}
}
private void tick() {
tickCount++;
// Update game state
updatePhysics();
updateAI();
updateCollisions();
if (tickCount % 1000 == 0) {
System.out.println("Tick: " + tickCount);
}
}
private void updatePhysics() {
// Physics calculations that benefit from OSR
for (int i = 0; i < 1000; i++) {
// Simulate physics calculations
double result = complexPhysicsCalculation(i);
}
}
private void updateAI() {
// AI calculations
for (int i = 0; i < 500; i++) {
// Simulate AI decision making
complexAICalculation(i);
}
}
private void updateCollisions() {
// Collision detection
for (int i = 0; i < 200; i++) {
// Simulate collision checks
complexCollisionCheck(i);
}
}
private void render(double interpolation) {
// Rendering logic
}
private double complexPhysicsCalculation(int index) {
return Math.sin(index * 0.1) * Math.cos(index * 0.1);
}
private void complexAICalculation(int index) {
// Simulate AI logic
double decision = Math.random() * index;
}
private boolean complexCollisionCheck(int index) {
// Simulate collision detection
return (index % 17) == 0;
}
public void stop() {
running = false;
}
}
}
// Data classes
public static class DataPoint {
private final long timestamp;
private double value;
private double processedValue;
public DataPoint(double value) {
this.timestamp = System.currentTimeMillis();
this.value = value;
}
public double getValue() { return value; }
public void setProcessedValue(double processedValue) { this.processedValue = processedValue; }
public long getTimestamp() { return timestamp; }
}
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
}
OSR Troubleshooting and Best Practices
Step 8: OSR Optimization Guidelines
package com.example.osr.bestpractices;
import java.util.*;
import java.util.concurrent.*;
public class OSRBestPractices {
// Patterns that benefit from OSR
public static class OSRFriendlyPatterns {
// 1. Long-running loops with substantial computation
public static long osrFriendlyLoop(int iterations) {
long result = 0;
for (int i = 0; i < iterations; i++) {
// Substantial computation per iteration
result += complexOperation(i);
// Avoid external calls that might inhibit optimization
// result += externalMethod(i); // ❌ Avoid in hot loops
}
return result;
}
// 2. Loops with predictable control flow
public static void predictableControlFlow(int[] data) {
for (int i = 0; i < data.length; i++) {
// Predictable if-condition
if (data[i] > 0) { // ✅ Predictable
data[i] = processPositive(data[i]);
} else {
data[i] = processNegative(data[i]);
}
}
}
// 3. Methods with focused, hot code regions
public static double focusedHotRegion(double[] values) {
double sum = 0;
double sumSquares = 0;
// Hot region - keep it focused
for (double value : values) {
sum += value;
sumSquares += value * value;
}
// Less critical computations outside hot region
double mean = sum / values.length;
double variance = sumSquares / values.length - mean * mean;
return Math.sqrt(variance);
}
private static long complexOperation(int n) {
// Keep this method small and inline-friendly
return (long) n * n + 2 * n + 1;
}
private static int processPositive(int value) {
return value * 2;
}
private static int processNegative(int value) {
return -value;
}
}
// Patterns to avoid for OSR
public static class OSRUnfriendlyPatterns {
// 1. Loops with frequent external calls
public static long unfriendlyExternalCalls(int iterations) {
long result = 0;
for (int i = 0; i < iterations; i++) {
// External call inhibits optimization
result += SomeExternalService.compute(i); // ❌ Inhibits OSR
}
return result;
}
// 2. Overly complex loop bodies
public static void overlyComplexLoop(int[] data) {
for (int i = 0; i < data.length; i++) {
// Too many different operations
data[i] = transformA(data[i]);
data[i] = transformB(data[i]);
data[i] = transformC(data[i]);
data[i] = transformD(data[i]);
data[i] = transformE(data[i]); // ❌ Too complex
// Better: Break into separate focused loops
}
}
// 3. Unpredictable control flow
public static void unpredictableBranching(int[] data, Random random) {
for (int i = 0; i < data.length; i++) {
// Unpredictable condition
if (random.nextBoolean()) { // ❌ Unpredictable
data[i] = processOptionA(data[i]);
} else {
data[i] = processOptionB(data[i]);
}
}
}
// 4. Loop with exception handling
public static void loopWithExceptions(List<String> data) {
for (String item : data) {
try {
processItem(item); // ❌ Exception handling inhibits
} catch (Exception e) {
// Handle exception
}
}
}
private static int transformA(int value) { return value + 1; }
private static int transformB(int value) { return value * 2; }
private static int transformC(int value) { return value - 1; }
private static int transformD(int value) { return value / 2; }
private static int transformE(int value) { return value % 10; }
private static int processOptionA(int value) { return value * 3; }
private static int processOptionB(int value) { return value / 3; }
private static void processItem(String item) { /* processing */ }
}
// External service simulation
public static class SomeExternalService {
public static long compute(int n) {
return n * 2L;
}
}
// OSR monitoring and debugging
public static class OSRDiagnostics {
public static void enableOSRDebugging() {
// JVM flags for OSR debugging (would be set as -XX parameters)
System.setProperty("jvm.debug.OSR", "true");
System.setProperty("jvm.print.compilation", "true");
System.setProperty("jvm.print.OSR", "true");
}
public static void monitorOSRBehavior() {
Thread monitoringThread = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
try {
// Monitor compilation events
printCompilationStats();
Thread.sleep(5000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
});
monitoringThread.setDaemon(true);
monitoringThread.start();
}
private static void printCompilationStats() {
// In real implementation, would use JVM MX beans
System.out.println("OSR Monitoring - " + new Date());
// Print relevant statistics
}
// Method to identify OSR opportunities
public static void analyzeMethodForOSR(String methodName, Runnable method) {
long startTime = System.nanoTime();
method.run();
long endTime = System.nanoTime();
long duration = endTime - startTime;
System.out.printf("Method %s took %d ns%n", methodName, duration);
if (duration > 1_000_000) { // 1ms threshold
System.out.println(" ⚡ Potential OSR candidate - long execution time");
}
}
}
// Best practices summary
public static class OSRGuidelines {
public static void printBestPractices() {
System.out.println("""
OSR Best Practices:
1. WRITE FOCUSED LOOPS
- Keep loop bodies focused on computation
- Avoid external method calls in hot loops
2. MINIMIZE BRANCH COMPLEXITY
- Use predictable conditions when possible
- Avoid complex control flow in hot loops
3. OPTIMIZE DATA ACCESS
- Use local variables for frequently accessed data
- Prefer primitive arrays over collections in critical loops
4. AVOID INHIBITING PATTERNS
- Remove exception handling from hot loops
- Minimize object allocation in performance-critical code
- Avoid synchronization in compute-intensive sections
5. ENABLE TIERED COMPILATION
- Use -XX:+TieredCompilation (enabled by default)
- Allow both C1 and C2 optimizations
6. MONITOR AND MEASURE
- Use -XX:+PrintCompilation to see OSR events
- Profile to identify actual hot spots
""");
}
}
}
Key Takeaways
OSR Benefits:
- Immediate Performance: Optimizes long-running methods without waiting for completion
- Adaptive Optimization: Responds to actual runtime behavior
- Progressive Optimization: Works with tiered compilation for gradual improvement
When OSR Matters:
- Long-running loops with substantial computation per iteration
- Server applications with continuous operation
- Scientific computing with intensive numerical methods
- Game engines with tight main loops
- Data processing pipelines handling large datasets
Optimization Guidelines:
- Focus hot loops - Keep critical sections clean and computation-focused
- Avoid inhibitors - Minimize external calls, exceptions, and complex control flow
- Enable monitoring - Use JVM flags to observe OSR behavior
- Profile realistically - Test with realistic data sizes and usage patterns
OSR is a powerful JVM feature that enables dynamic optimization of running code, particularly benefiting applications with long-running loops and continuous operation patterns.