Comprehensive guide to identifying, diagnosing, and fixing performance issues in Java applications.
1. Performance Monitoring and Profiling Tools
JVM Built-in Tools
// Performance monitoring utility class
public class PerformanceMonitor {
private static final Runtime runtime = Runtime.getRuntime();
public static void printMemoryUsage() {
long maxMemory = runtime.maxMemory();
long totalMemory = runtime.totalMemory();
long freeMemory = runtime.freeMemory();
long usedMemory = totalMemory - freeMemory;
System.out.printf("Memory Usage: Used=%dMB, Free=%dMB, Total=%dMB, Max=%dMB%n",
bytesToMB(usedMemory), bytesToMB(freeMemory),
bytesToMB(totalMemory), bytesToMB(maxMemory));
}
public static void printGCStats() {
for (GarbageCollectorMXBean gc : ManagementFactory.getGarbageCollectorMXBeans()) {
System.out.printf("GC: %s - Count=%d, Time=%dms%n",
gc.getName(), gc.getCollectionCount(), gc.getCollectionTime());
}
}
public static void printThreadStats() {
ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
System.out.printf("Threads: Live=%d, Peak=%d, Daemon=%d%n",
threadBean.getThreadCount(),
threadBean.getPeakThreadCount(),
threadBean.getDaemonThreadCount());
}
public static void monitorMethodPerformance(Runnable task, String taskName) {
long startTime = System.nanoTime();
long startMemory = runtime.totalMemory() - runtime.freeMemory();
task.run();
long endTime = System.nanoTime();
long endMemory = runtime.totalMemory() - runtime.freeMemory();
System.out.printf("Task: %s - Time=%dms, MemoryDelta=%d bytes%n",
taskName, (endTime - startTime) / 1_000_000, (endMemory - startMemory));
}
private static long bytesToMB(long bytes) {
return bytes / (1024 * 1024);
}
}
Using JMX for Monitoring
// Custom JMX MBean for performance monitoring
public interface PerformanceMBean {
long getTotalRequests();
double getAverageResponseTime();
int getActiveConnections();
void resetStats();
}
// MBean implementation
public class PerformanceMonitorMBean implements PerformanceMBean {
private final AtomicLong totalRequests = new AtomicLong();
private final AtomicLong totalResponseTime = new AtomicLong();
private final AtomicInteger activeConnections = new AtomicInteger();
@Override
public long getTotalRequests() {
return totalRequests.get();
}
@Override
public double getAverageResponseTime() {
long requests = totalRequests.get();
return requests > 0 ? (double) totalResponseTime.get() / requests : 0;
}
@Override
public int getActiveConnections() {
return activeConnections.get();
}
@Override
public void resetStats() {
totalRequests.set(0);
totalResponseTime.set(0);
}
public void recordRequest(long responseTime) {
totalRequests.incrementAndGet();
totalResponseTime.addAndGet(responseTime);
}
public void connectionOpened() {
activeConnections.incrementAndGet();
}
public void connectionClosed() {
activeConnections.decrementAndGet();
}
// Register MBean
public static void registerMBean() {
try {
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
ObjectName name = new ObjectName("com.example:type=PerformanceMonitor");
PerformanceMonitorMBean mbean = new PerformanceMonitorMBean();
mbs.registerMBean(mbean, name);
} catch (Exception e) {
throw new RuntimeException("Failed to register MBean", e);
}
}
}
2. Memory Optimization
Object Pooling
// Generic object pool implementation
public class ObjectPool<T> {
private final Supplier<T> creator;
private final Consumer<T> resetter;
private final Queue<T> pool;
private final int maxSize;
private final AtomicInteger createdCount = new AtomicInteger();
public ObjectPool(Supplier<T> creator, Consumer<T> resetter, int maxSize) {
this.creator = creator;
this.resetter = resetter;
this.maxSize = maxSize;
this.pool = new ConcurrentLinkedQueue<>();
}
public T borrowObject() {
T obj = pool.poll();
if (obj != null) {
return obj;
}
if (createdCount.get() < maxSize) {
createdCount.incrementAndGet();
return creator.get();
}
// Wait for object to be returned or create new one
try {
// In real implementation, you might want to wait with timeout
return creator.get();
} catch (Exception e) {
throw new RuntimeException("Failed to create object", e);
}
}
public void returnObject(T obj) {
if (obj != null) {
resetter.accept(obj);
if (pool.size() < maxSize) {
pool.offer(obj);
}
}
}
public int getPoolSize() {
return pool.size();
}
public int getCreatedCount() {
return createdCount.get();
}
}
// Usage example with expensive objects
public class ExpensiveObject {
private byte[] data = new byte[1024 * 1024]; // 1MB
private boolean initialized = false;
public void initialize() {
// Simulate expensive initialization
Arrays.fill(data, (byte) 1);
initialized = true;
}
public void reset() {
Arrays.fill(data, (byte) 0);
initialized = false;
}
public boolean process() {
return initialized;
}
}
// Pool usage
public class ObjectPoolExample {
private static final ObjectPool<ExpensiveObject> pool =
new ObjectPool<>(ExpensiveObject::new, ExpensiveObject::reset, 10);
public void processWithPool() {
ExpensiveObject obj = pool.borrowObject();
try {
obj.initialize();
obj.process();
} finally {
pool.returnObject(obj);
}
}
}
Memory-Efficient Collections
// Memory-optimized data structures
public class MemoryEfficientCollections {
// Primitive int list to avoid Integer boxing
public static class IntList {
private int[] data;
private int size;
public IntList(int initialCapacity) {
this.data = new int[initialCapacity];
this.size = 0;
}
public void add(int value) {
if (size == data.length) {
data = Arrays.copyOf(data, data.length * 2);
}
data[size++] = value;
}
public int get(int index) {
if (index < 0 || index >= size) {
throw new IndexOutOfBoundsException();
}
return data[index];
}
public int size() {
return size;
}
public void trimToSize() {
if (size < data.length) {
data = Arrays.copyOf(data, size);
}
}
}
// Flyweight pattern for frequently used objects
public static class StringFlyweight {
private final Map<String, String> pool = new ConcurrentHashMap<>();
public String getCanonical(String str) {
return pool.computeIfAbsent(str, k -> k);
}
public int getPoolSize() {
return pool.size();
}
}
// Memory-efficient map for enum keys
public static class EnumArrayMap<K extends Enum<K>, V> {
private final V[] values;
private final Class<K> keyType;
@SuppressWarnings("unchecked")
public EnumArrayMap(Class<K> keyType) {
this.keyType = keyType;
this.values = (V[]) new Object[keyType.getEnumConstants().length];
}
public void put(K key, V value) {
values[key.ordinal()] = value;
}
public V get(K key) {
return values[key.ordinal()];
}
public boolean containsKey(K key) {
return values[key.ordinal()] != null;
}
}
}
3. Garbage Collection Optimization
GC-Friendly Code Patterns
public class GarbageCollectionOptimization {
// 1. Object reuse pattern
public static class ObjectReuseExample {
private final ThreadLocal<StringBuilder> threadLocalStringBuilder =
ThreadLocal.withInitial(() -> new StringBuilder(1024));
public String buildMessage(String... parts) {
StringBuilder sb = threadLocalStringBuilder.get();
sb.setLength(0); // Reset instead of creating new
for (String part : parts) {
sb.append(part);
}
return sb.toString();
}
}
// 2. Avoid large object allocation in loops
public static class LoopOptimization {
// BAD: Creates new StringBuilder in each iteration
public void badLoop(List<String> items) {
for (String item : items) {
String result = new StringBuilder().append("Item: ").append(item).toString();
process(result);
}
}
// GOOD: Reuse StringBuilder
public void goodLoop(List<String> items) {
StringBuilder sb = new StringBuilder(128);
for (String item : items) {
sb.setLength(0);
sb.append("Item: ").append(item);
process(sb.toString());
}
}
}
// 3. Use primitive collections
public static class PrimitiveCollectionExample {
// BAD: Boxed integers
public int sumBad(List<Integer> numbers) {
int sum = 0;
for (Integer num : numbers) {
sum += num; // Auto-unboxing
}
return sum;
}
// GOOD: Primitive array
public int sumGood(int[] numbers) {
int sum = 0;
for (int num : numbers) {
sum += num;
}
return sum;
}
}
// 4. Soft/Weak references for cache
public static class MemorySensitiveCache<K, V> {
private final Map<K, SoftReference<V>> cache = new ConcurrentHashMap<>();
private final Function<K, V> loader;
public MemorySensitiveCache(Function<K, V> loader) {
this.loader = loader;
}
public V get(K key) {
SoftReference<V> ref = cache.get(key);
V value = ref != null ? ref.get() : null;
if (value == null) {
value = loader.apply(key);
cache.put(key, new SoftReference<>(value));
}
return value;
}
public void cleanUp() {
cache.entrySet().removeIf(entry -> entry.getValue().get() == null);
}
}
}
GC Tuning Examples
// JVM Flag recommendations for different scenarios
public class GCTuningGuide {
/*
* Throughput Optimized (Web Servers)
* -XX:+UseG1GC -Xmx4g -Xms4g -XX:MaxGCPauseMillis=200
* -XX:ParallelGCThreads=4 -XX:ConcGCThreads=2
*/
public static class ThroughputOptimized {
// G1GC for predictable pause times
}
/*
* Low Latency (Trading Systems)
* -XX:+UseZGC -Xmx8g -Xms8g -XX:MaxGCPauseMillis=10
* -XX:ConcGCThreads=4
*/
public static class LowLatency {
// ZGC for sub-millisecond pauses
}
/*
* Memory Constrained (Containers)
* -XX:+UseSerialGC -Xmx512m -Xms512m
* -XX:MaxMetaspaceSize=128m -XX:ReservedCodeCacheSize=64m
*/
public static class MemoryConstrained {
// SerialGC for small heaps
}
}
// Monitoring GC behavior
public class GCMonitor {
private static final Logger logger = Logger.getLogger(GCMonitor.class.getName());
public static void setupGCMonitoring() {
List<GarbageCollectorMXBean> gcBeans = ManagementFactory.getGarbageCollectorMXBeans();
Timer timer = new Timer(true);
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
for (GarbageCollectorMXBean gc : gcBeans) {
long count = gc.getCollectionCount();
long time = gc.getCollectionTime();
if (count > 0) {
logger.info(String.format("GC %s: Collections=%d, TotalTime=%dms, AvgTime=%.2fms",
gc.getName(), count, time, (double) time / count));
}
}
// Log memory usage
MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean();
MemoryUsage heapUsage = memoryBean.getHeapMemoryUsage();
MemoryUsage nonHeapUsage = memoryBean.getNonHeapMemoryUsage();
logger.info(String.format("Heap: used=%dMB, committed=%dMB, max=%dMB",
heapUsage.getUsed() / 1024 / 1024,
heapUsage.getCommitted() / 1024 / 1024,
heapUsage.getMax() / 1024 / 1024));
}
}, 0, 60000); // Log every minute
}
}
4. CPU and Algorithm Optimization
Efficient Algorithms and Data Structures
public class AlgorithmOptimization {
// 1. Use appropriate data structures
public static class DataStructureSelection {
// For frequent lookups by key
public void hashMapExample() {
Map<String, Integer> map = new HashMap<>();
// O(1) average case for get/put
}
// For range queries or sorted iteration
public void treeMapExample() {
Map<String, Integer> map = new TreeMap<>();
// O(log n) for get/put, maintains order
}
// For frequent insertions at both ends
public void arrayDequeExample() {
Deque<String> deque = new ArrayDeque<>();
// O(1) for add/remove at both ends
}
}
// 2. Algorithm complexity optimization
public static class AlgorithmComplexity {
// O(n^2) - Bad for large datasets
public boolean hasDuplicateBad(List<String> list) {
for (int i = 0; i < list.size(); i++) {
for (int j = i + 1; j < list.size(); j++) {
if (list.get(i).equals(list.get(j))) {
return true;
}
}
}
return false;
}
// O(n) - Good for large datasets
public boolean hasDuplicateGood(List<String> list) {
Set<String> seen = new HashSet<>();
for (String item : list) {
if (!seen.add(item)) {
return true;
}
}
return false;
}
}
// 3. Cache-friendly data access
public static class CacheFriendly {
// Bad: Non-contiguous memory access
public void processLinkedList(LinkedList<Integer> list) {
for (Integer num : list) {
// Each access may cause cache miss
process(num);
}
}
// Good: Contiguous memory access
public void processArrayList(ArrayList<Integer> list) {
for (int i = 0; i < list.size(); i++) {
// Sequential memory access - cache friendly
process(list.get(i));
}
}
// Even better: Primitive array
public void processArray(int[] array) {
for (int i = 0; i < array.length; i++) {
// Most cache-friendly
process(array[i]);
}
}
}
// 4. Early termination and short-circuiting
public static class EarlyTermination {
public boolean containsSpecialBad(List<String> list) {
boolean found = false;
for (String item : list) {
if (isSpecial(item)) {
found = true;
}
}
return found;
}
public boolean containsSpecialGood(List<String> list) {
for (String item : list) {
if (isSpecial(item)) {
return true; // Early termination
}
}
return false;
}
}
// Helper methods
private static void process(int num) {
// Simulate processing
}
private static boolean isSpecial(String item) {
return item.startsWith("special");
}
}
JIT Optimization Techniques
public class JITOptimization {
// 1. Method inlining friendly
public static class InliningFriendly {
// Small methods are more likely to be inlined
public int calculate(int a, int b) {
return add(multiply(a, b), subtract(a, b));
}
private int add(int a, int b) {
return a + b; // Likely to be inlined
}
private int multiply(int a, int b) {
return a * b; // Likely to be inlined
}
private int subtract(int a, int b) {
return a - b; // Likely to be inlined
}
}
// 2. Avoid megamorphic call sites
public static class PolymorphismOptimization {
// Monomorphic (fastest) - single implementation
public int processMonomorphic(Processor processor) {
return processor.process(); // JIT can devirtualize
}
// Bimorphic (fast) - two implementations
public int processBimorphic(Processor processor) {
return processor.process(); // JIT can handle
}
// Megamorphic (slow) - many implementations
public int processMegamorphic(Processor processor) {
return processor.process(); // Virtual call needed
}
}
// 3. Branch prediction friendly
public static class BranchPrediction {
// Data with predictable patterns
public int sumEvenNumbers(int[] numbers) {
int sum = 0;
for (int num : numbers) {
if (num % 2 == 0) { // Predictable pattern
sum += num;
}
}
return sum;
}
// Sort data to make branches predictable
public void processSortedData(int[] data) {
Arrays.sort(data); // Branches become more predictable
for (int value : data) {
if (value > 1000) { // Becomes predictable after sorting
processHighValue(value);
}
}
}
}
// 4. Loop optimizations
public static class LoopOptimizations {
// Loop unrolling
public int sumArray(int[] array) {
int sum = 0;
int i = 0;
// Unrolled loop - process 4 elements at a time
for (; i <= array.length - 4; i += 4) {
sum += array[i] + array[i + 1] + array[i + 2] + array[i + 3];
}
// Process remaining elements
for (; i < array.length; i++) {
sum += array[i];
}
return sum;
}
// Avoid method calls in hot loops
public void optimizedLoop(List<String> items) {
int size = items.size(); // Cache size
for (int i = 0; i < size; i++) { // Avoid method call in condition
String item = items.get(i); // Direct access
processItem(item);
}
}
}
}
interface Processor {
int process();
}
class SimpleProcessor implements Processor {
@Override
public int process() {
return 1;
}
}
class ComplexProcessor implements Processor {
@Override
public int process() {
return 2;
}
}
5. I/O and Database Optimization
Efficient I/O Operations
public class IOOptimization {
// 1. Use buffered I/O
public static class BufferedIO {
// Bad: Unbuffered I/O
public void copyFileUnbuffered(Path source, Path target) throws IOException {
try (InputStream is = Files.newInputStream(source);
OutputStream os = Files.newOutputStream(target)) {
int data;
while ((data = is.read()) != -1) { // Single byte reads - very slow!
os.write(data);
}
}
}
// Good: Buffered I/O
public void copyFileBuffered(Path source, Path target) throws IOException {
try (InputStream is = new BufferedInputStream(Files.newInputStream(source));
OutputStream os = new BufferedOutputStream(Files.newOutputStream(target))) {
byte[] buffer = new byte[8192]; // 8KB buffer
int bytesRead;
while ((bytesRead = is.read(buffer)) != -1) {
os.write(buffer, 0, bytesRead);
}
}
}
}
// 2. Use NIO for large files
public static class NIOOptimization {
public void copyFileNIO(Path source, Path target) throws IOException {
try (FileChannel sourceChannel = FileChannel.open(source, StandardOpenOption.READ);
FileChannel targetChannel = FileChannel.open(target,
StandardOpenOption.WRITE, StandardOpenOption.CREATE)) {
long size = sourceChannel.size();
long position = 0;
while (position < size) {
position += sourceChannel.transferTo(position, 64 * 1024 * 1024, targetChannel);
}
}
}
}
// 3. Connection pooling for databases
public static class ConnectionPool {
private final BlockingQueue<Connection> pool;
private final AtomicInteger createdCount = new AtomicInteger();
private final int maxPoolSize;
public ConnectionPool(int maxPoolSize) {
this.maxPoolSize = maxPoolSize;
this.pool = new LinkedBlockingQueue<>(maxPoolSize);
}
public Connection getConnection() throws SQLException {
Connection conn = pool.poll();
if (conn != null && !conn.isClosed()) {
return conn;
}
if (createdCount.get() < maxPoolSize) {
createdCount.incrementAndGet();
return createNewConnection();
}
try {
return pool.take(); // Wait for available connection
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new SQLException("Interrupted while waiting for connection", e);
}
}
public void returnConnection(Connection conn) {
if (conn != null) {
pool.offer(conn);
}
}
private Connection createNewConnection() throws SQLException {
// Implementation to create new database connection
return null;
}
}
// 4. Batch database operations
public static class BatchOperations {
public void insertUsersBad(List<User> users, Connection conn) throws SQLException {
String sql = "INSERT INTO users (id, name, email) VALUES (?, ?, ?)";
try (PreparedStatement stmt = conn.prepareStatement(sql)) {
for (User user : users) {
stmt.setLong(1, user.getId());
stmt.setString(2, user.getName());
stmt.setString(3, user.getEmail());
stmt.executeUpdate(); // One round-trip per insert
}
}
}
public void insertUsersGood(List<User> users, Connection conn) throws SQLException {
String sql = "INSERT INTO users (id, name, email) VALUES (?, ?, ?)";
try (PreparedStatement stmt = conn.prepareStatement(sql)) {
for (User user : users) {
stmt.setLong(1, user.getId());
stmt.setString(2, user.getName());
stmt.setString(3, user.getEmail());
stmt.addBatch(); // Add to batch
// Execute in batches of 100
if (users.indexOf(user) % 100 == 0) {
stmt.executeBatch();
}
}
stmt.executeBatch(); // Execute remaining
}
}
}
}
class User {
private long id;
private String name;
private String email;
// constructor, getters, setters
public User(long id, String name, String email) {
this.id = id;
this.name = name;
this.email = email;
}
public long getId() { return id; }
public String getName() { return name; }
public String getEmail() { return email; }
}
6. Concurrency Optimization
Efficient Concurrent Programming
public class ConcurrencyOptimization {
// 1. Use appropriate concurrent collections
public static class ConcurrentCollections {
private final ConcurrentHashMap<String, Integer> concurrentMap = new ConcurrentHashMap<>();
private final CopyOnWriteArrayList<String> copyOnWriteList = new CopyOnWriteArrayList<>();
private final ConcurrentLinkedQueue<String> concurrentQueue = new ConcurrentLinkedQueue<>();
// Use ConcurrentHashMap for high-concurrency maps
public void updateCounter(String key) {
concurrentMap.compute(key, (k, v) -> v == null ? 1 : v + 1);
}
// Use CopyOnWriteArrayList for read-heavy, write-rarely scenarios
public List<String> getSnapshot() {
return new ArrayList<>(copyOnWriteList);
}
}
// 2. Reduce lock contention
public static class LockContentionReduction {
// Bad: Single lock for all operations
private final Object globalLock = new Object();
private int counter1 = 0;
private int counter2 = 0;
public void incrementBad() {
synchronized (globalLock) {
counter1++;
counter2++;
}
}
// Good: Separate locks for independent operations
private final Object lock1 = new Object();
private final Object lock2 = new Object();
public void incrementGood() {
synchronized (lock1) {
counter1++;
}
synchronized (lock2) {
counter2++;
}
}
// Better: Use Atomic variables when possible
private final AtomicInteger atomicCounter1 = new AtomicInteger();
private final AtomicInteger atomicCounter2 = new AtomicInteger();
public void incrementBest() {
atomicCounter1.incrementAndGet();
atomicCounter2.incrementAndGet();
}
}
// 3. Thread pool optimization
public static class ThreadPoolOptimization {
// CPU-bound tasks
public ExecutorService createCpuBoundPool() {
int cores = Runtime.getRuntime().availableProcessors();
return Executors.newFixedThreadPool(cores);
}
// I/O bound tasks
public ExecutorService createIoBoundPool() {
return Executors.newCachedThreadPool();
}
// Scheduled tasks
public ScheduledExecutorService createScheduledPool() {
return Executors.newScheduledThreadPool(4);
}
// Custom thread pool with monitoring
public ThreadPoolExecutor createMonitoredPool(int coreSize, int maxSize) {
return new ThreadPoolExecutor(
coreSize, maxSize, 60L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(1000),
new ThreadFactory() {
private final AtomicInteger counter = new AtomicInteger();
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r, "worker-" + counter.incrementAndGet());
t.setDaemon(true);
return t;
}
},
new ThreadPoolExecutor.CallerRunsPolicy() // Handle rejection
) {
@Override
protected void beforeExecute(Thread t, Runnable r) {
System.out.println("Starting task in thread: " + t.getName());
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
if (t != null) {
System.err.println("Task failed: " + t.getMessage());
}
}
};
}
}
// 4. Non-blocking algorithms
public static class NonBlockingCounter {
private final AtomicLong counter = new AtomicLong();
private final LongAdder highPerformanceCounter = new LongAdder();
// Simple atomic counter
public long incrementAndGet() {
return counter.incrementAndGet();
}
// High-performance counter for high contention
public long incrementAndGetHighPerf() {
highPerformanceCounter.increment();
return highPerformanceCounter.sum();
}
}
// 5. CompletableFuture for async programming
public static class AsyncOptimization {
public CompletableFuture<String> processAsync(String input) {
return CompletableFuture.supplyAsync(() -> expensiveOperation(input))
.thenApplyAsync(this::transformResult)
.exceptionally(this::handleError);
}
public CompletableFuture<Void> processMultipleAsync(List<String> inputs) {
List<CompletableFuture<String>> futures = inputs.stream()
.map(this::processAsync)
.toList();
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
}
private String expensiveOperation(String input) {
// Simulate expensive operation
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return input.toUpperCase();
}
private String transformResult(String result) {
return "Processed: " + result;
}
private String handleError(Throwable throwable) {
return "Error: " + throwable.getMessage();
}
}
}
7. JVM Tuning and Configuration
JVM Flag Optimization
// JVM tuning configurations for different scenarios
public class JVMTuningConfigs {
/*
* Web Application Server (Spring Boot, Tomcat)
*/
public static class WebAppTuning {
// Recommended JVM flags:
String[] flags = {
"-server", // Server VM
"-Xmx4g", "-Xms4g", // Fixed heap size
"-XX:+UseG1GC", // G1 Garbage Collector
"-XX:MaxGCPauseMillis=200", // Target max pause time
"-XX:ParallelGCThreads=4", // Parallel GC threads
"-XX:ConcGCThreads=2", // Concurrent GC threads
"-XX:+AlwaysPreTouch", // Pre-touch memory pages
"-XX:+UseStringDeduplication", // Deduplicate strings
"-XX:+UseCompressedOops", // Compress object pointers
"-XX:MetaspaceSize=256m", // Metaspace initial size
"-XX:MaxMetaspaceSize=512m", // Metaspace max size
"-Djava.security.egd=file:/dev/./urandom" // Faster random
};
}
/*
* High-Performance Computing
*/
public static class HighPerformanceTuning {
// Recommended JVM flags:
String[] flags = {
"-server",
"-Xmx16g", "-Xms16g",
"-XX:+UseZGC", // ZGC for low latency
"-XX:MaxGCPauseMillis=10", // Very low pause target
"-XX:+UseLargePages", // Use large memory pages
"-XX:+UseTransparentHugePages",
"-XX:+UseNUMA", // NUMA awareness
"-XX:+AggressiveOpts", // Aggressive optimizations
"-XX:+UseBiasedLocking", // Biased locking
"-XX:ReservedCodeCacheSize=512m", // Larger code cache
"-XX:InitialCodeCacheSize=256m"
};
}
/*
* Microservices / Containerized
*/
public static class ContainerTuning {
// Recommended JVM flags:
String[] flags = {
"-XX:+UseContainerSupport", // Container awareness
"-XX:MaxRAMPercentage=75.0", // Use 75% of container memory
"-XX:InitialRAMPercentage=50.0", // Initial memory percentage
"-XX:MinRAMPercentage=25.0", // Minimum memory percentage
"-XX:+UseG1GC",
"-XX:MaxGCPauseMillis=100",
"-XX:+ExitOnOutOfMemoryError", // Exit on OOM
"-XX:+CrashOnOutOfMemoryError", // Crash on OOM
"-XX:NativeMemoryTracking=detail", // NMT for memory analysis
"-Xlog:gc*:file=/logs/gc.log:time" // GC logging
};
}
}
// Runtime optimization based on environment
public class AdaptiveOptimization {
private static final boolean IS_CONTAINER =
System.getenv("KUBERNETES_SERVICE_HOST") != null;
private static final int AVAILABLE_CORES =
Runtime.getRuntime().availableProcessors();
private static final long MAX_MEMORY =
Runtime.getRuntime().maxMemory();
public static void applyOptimalSettings() {
if (IS_CONTAINER) {
applyContainerSettings();
} else {
applyPhysicalServerSettings();
}
// Log current JVM settings
logJVMSettings();
}
private static void applyContainerSettings() {
System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism",
String.valueOf(Math.max(2, AVAILABLE_CORES / 2)));
}
private static void applyPhysicalServerSettings() {
System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism",
String.valueOf(AVAILABLE_CORES));
}
private static void logJVMSettings() {
System.out.println("JVM Settings:");
System.out.println(" Cores: " + AVAILABLE_CORES);
System.out.println(" Max Memory: " + (MAX_MEMORY / 1024 / 1024) + "MB");
System.out.println(" Common Parallelism: " +
System.getProperty("java.util.concurrent.ForkJoinPool.common.parallelism"));
System.out.println(" GC: " +
ManagementFactory.getGarbageCollectorMXBeans().stream()
.map(GarbageCollectorMXBean::getName)
.collect(Collectors.joining(", ")));
}
}
Performance Testing Framework
// Simple performance testing utility
public class PerformanceTest {
private final String testName;
private final int warmupIterations;
private final int measurementIterations;
public PerformanceTest(String testName, int warmupIterations, int measurementIterations) {
this.testName = testName;
this.warmupIterations = warmupIterations;
this.measurementIterations = measurementIterations;
}
public TestResult runTest(Runnable testCode) {
// Warmup phase
for (int i = 0; i < warmupIterations; i++) {
testCode.run();
}
// Measurement phase
long totalTime = 0;
long minTime = Long.MAX_VALUE;
long maxTime = Long.MIN_VALUE;
for (int i = 0; i < measurementIterations; i++) {
long startTime = System.nanoTime();
testCode.run();
long endTime = System.nanoTime();
long duration = endTime - startTime;
totalTime += duration;
minTime = Math.min(minTime, duration);
maxTime = Math.max(maxTime, duration);
}
long avgTime = totalTime / measurementIterations;
return new TestResult(testName, avgTime, minTime, maxTime,
warmupIterations, measurementIterations);
}
public static class TestResult {
private final String testName;
private final long averageTimeNs;
private final long minTimeNs;
private final long maxTimeNs;
private final int warmupIterations;
private final int measurementIterations;
public TestResult(String testName, long averageTimeNs, long minTimeNs,
long maxTimeNs, int warmupIterations, int measurementIterations) {
this.testName = testName;
this.averageTimeNs = averageTimeNs;
this.minTimeNs = minTimeNs;
this.maxTimeNs = maxTimeNs;
this.warmupIterations = warmupIterations;
this.measurementIterations = measurementIterations;
}
@Override
public String toString() {
return String.format(
"Test: %s\n" +
" Iterations: %d warmup, %d measurement\n" +
" Average: %.3f ms\n" +
" Min: %.3f ms\n" +
" Max: %.3f ms\n" +
" Throughput: %.2f ops/sec",
testName, warmupIterations, measurementIterations,
averageTimeNs / 1_000_000.0,
minTimeNs / 1_000_000.0,
maxTimeNs / 1_000_000.0,
1_000_000_000.0 / averageTimeNs
);
}
}
}
// Usage example
public class PerformanceTestExample {
public static void main(String[] args) {
PerformanceTest test = new PerformanceTest("String Concatenation", 1000, 10000);
TestResult result1 = test.runTest(() -> {
// Test method 1
String result = "";
for (int i = 0; i < 100; i++) {
result += "test" + i;
}
});
TestResult result2 = test.runTest(() -> {
// Test method 2
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100; i++) {
sb.append("test").append(i);
}
String result = sb.toString();
});
System.out.println(result1);
System.out.println(result2);
}
}
Key Performance Tuning Principles:
- Measure First: Always profile before optimizing
- Memory Efficiency: Reduce object creation, use appropriate data structures
- Algorithm Selection: Choose algorithms with better complexity
- Concurrency: Use appropriate synchronization and thread pools
- I/O Optimization: Use buffering, batching, and async operations
- JVM Tuning: Configure GC and JVM settings for your workload
- Cache-Friendly: Optimize data access patterns for CPU cache
- Avoid Premature Optimization: Focus on bottlenecks that actually matter
This comprehensive guide covers the essential aspects of Java performance tuning, from basic monitoring to advanced optimization techniques.