Skip to content

Java Performance Tips

This guide covers Java performance optimization techniques, best practices, and tools for writing efficient Java applications.

🎯 Core Performance Principles

JVM Understanding

  • Garbage Collection: Understand GC algorithms and tuning
  • Memory Management: Heap vs. stack, object lifecycle
  • JIT Compilation: HotSpot optimization and warm-up
  • Class Loading: Impact on startup performance

Measurement First

  • Profile Before Optimizing: Use profilers to identify bottlenecks
  • Benchmark Properly: Use JMH for microbenchmarks
  • Monitor Production: Use APM tools for real-world performance
  • Set Performance Goals: Define measurable performance targets

🔧 Memory Optimization

Object Creation

// Bad: Creating many temporary objects
public String concatenateBad(List<String> items) {
    String result = "";
    for (String item : items) {
        result += item;  // Creates new String each iteration
    }
    return result;
}

// Good: Use StringBuilder
public String concatenateGood(List<String> items) {
    StringBuilder sb = new StringBuilder();
    for (String item : items) {
        sb.append(item);
    }
    return sb.toString();
}

// Better: Use String.join (Java 8+)
public String concatenateBest(List<String> items) {
    return String.join("", items);
}

Collection Usage

// Bad: Using wrong collection type
List<Integer> numbers = new ArrayList<>();
for (int i = 0; i < 1000000; i++) {
    numbers.add(0, i);  // O(n) operation - very slow!
}

// Good: Use appropriate collection
List<Integer> numbers = new LinkedList<>();
for (int i = 0; i < 1000000; i++) {
    numbers.add(0, i);  // O(1) operation
}

// Better: Use ArrayList and add at end
List<Integer> numbers = new ArrayList<>(1000000);
for (int i = 0; i < 1000000; i++) {
    numbers.add(i);  // O(1) amortized
}

Memory Leaks Prevention

// Bad: Static references to objects
public class MemoryLeak {
    private static List<Object> cache = new ArrayList<>();

    public void addToCache(Object obj) {
        cache.add(obj);  // Objects never garbage collected!
    }
}

// Good: Use weak references for caches
public class GoodCache {
    private static Map<String, WeakReference<Object>> cache = new HashMap<>();

    public void addToCache(String key, Object obj) {
        cache.put(key, new WeakReference<>(obj));
    }

    public Object getFromCache(String key) {
        WeakReference<Object> ref = cache.get(key);
        return ref != null ? ref.get() : null;
    }
}

🚀 Algorithm Optimization

Loop Optimization

// Bad: Inefficient loop
public int findMax(int[] array) {
    int max = Integer.MIN_VALUE;
    for (int i = 0; i < array.length; i++) {
        if (array[i] > max) {
            max = array[i];
        }
    }
    return max;
}

// Good: Enhanced for-loop (cleaner, similar performance)
public int findMaxEnhanced(int[] array) {
    int max = Integer.MIN_VALUE;
    for (int value : array) {
        if (value > max) {
            max = value;
        }
    }
    return max;
}

// Better: Use streams (Java 8+)
public int findMaxStream(int[] array) {
    return Arrays.stream(array).max().orElse(Integer.MIN_VALUE);
}

String Operations

// Bad: Repeated string operations
public boolean containsKeywords(String text, List<String> keywords) {
    for (String keyword : keywords) {
        if (text.toLowerCase().contains(keyword.toLowerCase())) {
            return true;
        }
    }
    return false;
}

// Good: Compile patterns once
public class KeywordMatcher {
    private final List<Pattern> patterns;

    public KeywordMatcher(List<String> keywords) {
        this.patterns = keywords.stream()
            .map(keyword -> Pattern.compile(Pattern.quote(keyword), Pattern.CASE_INSENSITIVE))
            .collect(Collectors.toList());
    }

    public boolean containsKeywords(String text) {
        return patterns.stream().anyMatch(pattern -> pattern.matcher(text).find());
    }
}

📊 Concurrency Performance

Thread Pool Usage

// Bad: Creating threads manually
public void processTasks(List<Runnable> tasks) {
    for (Runnable task : tasks) {
        new Thread(task).start();  // Creates unlimited threads!
    }
}

// Good: Use thread pool
public void processTasksGood(List<Runnable> tasks) {
    ExecutorService executor = Executors.newFixedThreadPool(
        Runtime.getRuntime().availableProcessors()
    );

    for (Runnable task : tasks) {
        executor.submit(task);
    }

    executor.shutdown();
    try {
        executor.awaitTermination(1, TimeUnit.MINUTES);
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
}

// Better: Use parallel streams (Java 8+)
public void processTasksParallel(List<Runnable> tasks) {
    tasks.parallelStream().forEach(Runnable::run);
}

Synchronization

// Bad: Excessive synchronization
public class SynchronizedCounter {
    private int count = 0;

    public synchronized void increment() {
        count++;  // Synchronized even for single-threaded use
    }

    public synchronized int getCount() {
        return count;
    }
}

// Good: Use atomic classes for simple operations
public class AtomicCounter {
    private final AtomicInteger count = new AtomicInteger(0);

    public void increment() {
        count.incrementAndGet();  // Lock-free operation
    }

    public int getCount() {
        return count.get();
    }
}

// Better: Use LongAdder for high contention
public class HighPerformanceCounter {
    private final LongAdder count = new LongAdder();

    public void increment() {
        count.increment();  // Better under high contention
    }

    public long getCount() {
        return count.sum();
    }
}

🛠️ I/O Performance

File Operations

// Bad: Reading file byte by byte
public String readFileBad(String filename) throws IOException {
    FileInputStream fis = new FileInputStream(filename);
    String content = "";
    int byteRead;

    while ((byteRead = fis.read()) != -1) {
        content += (char) byteRead;  // Very slow!
    }

    fis.close();
    return content;
}

// Good: Use buffered reading
public String readFileGood(String filename) throws IOException {
    try (BufferedReader reader = Files.newBufferedReader(Paths.get(filename))) {
        StringBuilder content = new StringBuilder();
        String line;

        while ((line = reader.readLine()) != null) {
            content.append(line).append("\n");
        }

        return content.toString();
    }
}

// Better: Use Files.readAllLines for small files
public List<String> readFileLines(String filename) throws IOException {
    return Files.readAllLines(Paths.get(filename));
}

// Best: Use Files.readString (Java 11+)
public String readFileString(String filename) throws IOException {
    return Files.readString(Paths.get(filename));
}

Network I/O

// Bad: Synchronous I/O blocking
public String fetchUrlBad(String url) throws IOException {
    URLConnection connection = new URL(url).openConnection();
    BufferedReader reader = new BufferedReader(
        new InputStreamReader(connection.getInputStream())
    );

    StringBuilder response = new StringBuilder();
    String line;
    while ((line = reader.readLine()) != null) {
        response.append(line);
    }

    reader.close();
    return response.toString();
}

// Good: Use HttpClient (Java 11+)
public CompletableFuture<String> fetchUrlAsync(String url) {
    HttpClient client = HttpClient.newHttpClient();
    HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create(url))
        .build();

    return client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
        .thenApply(HttpResponse::body);
}

📈 JVM Tuning

Garbage Collection

# G1GC (Good for most applications)
java -XX:+UseG1GC -XX:MaxGCPauseMillis=200 MyApp

# ZGC (Low latency, large heaps)
java -XX:+UseZGC MyApp

# Parallel GC (High throughput)
java -XX:+UseParallelGC -XX:ParallelGCThreads=4 MyApp

Memory Settings

# Set heap size
java -Xms512m -Xmx2g MyApp

# Set metaspace size
java -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m MyApp

# Optimize for containers
java -XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0 MyApp

🔍 Performance Monitoring

JVM Tools

# JVisualVM - GUI monitoring
jvisualvm

# JConsole - Basic monitoring
jconsole

# JFR - Flight Recorder (Java 11+)
java -XX:StartFlightRecording=duration=60s,filename=recording.jfr MyApp

# JHSDB - Serviceability agent
jhsdb jmap --pid <pid> --heap

Profiling Tools

// Use JMH for microbenchmarks
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Thread)
public class MyBenchmark {

    @Benchmark
    public String stringConcatenation() {
        String result = "";
        for (int i = 0; i < 1000; i++) {
            result += i;
        }
        return result;
    }

    @Benchmark
    public String stringBuilderConcatenation() {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < 1000; i++) {
            sb.append(i);
        }
        return sb.toString();
    }
}

🔗 Language-Specific Performance