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.