10 Java Performance Tips Every Developer Should Know
Java performance optimization is an art that combines deep JVM knowledge with practical programming techniques. Here are 10 essential tips I want to share with you.
1. Use StringBuilder for String Concatenation
❌ Incorrect:
String result = "";
for (int i = 0; i < 1000; i++) {
result += "item" + i;
}
✅ Correct:
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
sb.append("item").append(i);
}
String result = sb.toString();
2. Optimize Collection Usage
ArrayList vs LinkedList
- ArrayList: Better for random access
- LinkedList: Better for frequent insertions/deletions
// For frequent index-based access
List<String> list = new ArrayList<>();
// For frequent insertions/deletions
List<String> list = new LinkedList<>();
HashMap vs TreeMap
// O(1) for basic operations
Map<String, Integer> hashMap = new HashMap<>();
// O(log n) but maintains order
Map<String, Integer> treeMap = new TreeMap<>();
3. Lazy Loading of Resources
public class ExpensiveResource {
private static volatile ExpensiveResource instance;
public static ExpensiveResource getInstance() {
if (instance == null) {
synchronized (ExpensiveResource.class) {
if (instance == null) {
instance = new ExpensiveResource();
}
}
}
return instance;
}
}
4. Object Pooling for Expensive Resources
public class DatabaseConnectionPool {
private final Queue<Connection> pool = new ConcurrentLinkedQueue<>();
private final int maxSize;
public Connection getConnection() {
Connection conn = pool.poll();
if (conn == null) {
conn = createNewConnection();
}
return conn;
}
public void returnConnection(Connection conn) {
if (pool.size() < maxSize) {
pool.offer(conn);
} else {
conn.close();
}
}
}
5. Use Streams Carefully
For simple operations, traditional loops can be faster:
// Stream (more readable but potentially slower)
list.stream()
.filter(x -> x > 10)
.mapToInt(x -> x * 2)
.sum();
// Traditional loop (faster for simple operations)
int sum = 0;
for (int x : list) {
if (x > 10) {
sum += x * 2;
}
}
6. JVM Optimization
Important parameters:
# Heap size
-Xms2g -Xmx4g
# Garbage Collector
-XX:+UseG1GC
# JIT Compiler
-XX:+UseCompressedOops
# Monitoring
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
7. Avoid Unnecessary Autoboxing
❌ Incorrect:
Integer sum = 0;
for (int i = 0; i < 1000; i++) {
sum += i; // Autoboxing in each iteration
}
✅ Correct:
int sum = 0;
for (int i = 0; i < 1000; i++) {
sum += i;
}
Integer result = sum; // Autoboxing only once
8. Use Parallel Streams for CPU-Intensive Operations
// For large lists and expensive operations
list.parallelStream()
.filter(this::expensiveFilter)
.map(this::expensiveTransformation)
.collect(Collectors.toList());
⚠️ Warning: Don’t use parallel streams for:
- Small lists (< 1000 elements)
- I/O bound operations
- Very fast operations
9. Optimize Database Access
Batch Operations
// ❌ Multiple queries
for (User user : users) {
userRepository.save(user);
}
// ✅ Batch insert
userRepository.saveAll(users);
Connection Pooling
@Configuration
public class DatabaseConfig {
@Bean
public DataSource dataSource() {
HikariConfig config = new HikariConfig();
config.setMaximumPoolSize(20);
config.setMinimumIdle(5);
config.setConnectionTimeout(30000);
return new HikariDataSource(config);
}
}
10. Profiling and Monitoring
Essential tools:
- JProfiler: For detailed memory and CPU analysis
- VisualVM: Free tool included with JDK
- JConsole: For basic monitoring
- Micrometer: For metrics in Spring Boot applications
Example with Micrometer:
@Component
public class PerformanceMetrics {
private final MeterRegistry meterRegistry;
private final Timer.Sample sample;
@EventListener
public void handleMethodExecution(MethodExecutionEvent event) {
Timer.Sample.start(meterRegistry)
.stop(Timer.builder("method.execution")
.tag("method", event.getMethodName())
.register(meterRegistry));
}
}
Benchmarking Tools
JMH (Java Microbenchmark Harness)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Benchmark)
public class StringConcatenationBenchmark {
@Benchmark
public String stringBuilder() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100; i++) {
sb.append("test");
}
return sb.toString();
}
@Benchmark
public String stringConcatenation() {
String result = "";
for (int i = 0; i < 100; i++) {
result += "test";
}
return result;
}
}
Conclusion
Java performance optimization requires a systematic approach:
- Measure first: Don’t optimize without data
- Identify bottlenecks: Use profilers
- Optimize the critical: Focus on the 20% that causes 80% of problems
- Verify improvements: Measure after each optimization
Remember: “Premature optimization is the root of all evil” - Donald Knuth
What optimization techniques have you found most effective in your projects?