Profiling Java Code: Finding Bottlenecks in Practice
Every Java developer sooner or later faces performance issues: the application runs slowly, consumes too much memory, or unexpectedly "freezes." In such situations, profiling is indispensable. In this article, we will explore how to practically find bottlenecks in Java code using modern profiling tools.
What is profiling and why is it needed?
Profiling is a dynamic analysis of a program that allows you to collect data about its operation: CPU load, memory usage, method execution time, number of created objects, and threads. Unlike logging, profiling provides an accurate picture of "bottlenecks" without the need to manually set timers.
Main types of profiling:
- CPU profiling — determines which methods consume the most processor time.
- Memory profiling — identifies memory leaks, object creation frequency, and garbage collector (GC) activity.
- Thread profiling — analyzes locks, deadlocks, and thread states.
Tools for Profiling Java
Let's look at three of the most popular tools that will help you find bottlenecks.
1. VisualVM
A free tool included in the JDK. Great for quick analysis of a running application. It allows you to monitor CPU, memory, threads, and even perform heap dumps.
// Launching VisualVM from the command linejvisualvm.exe2. JProfiler
A commercial profiler with a powerful set of features: detailed analysis of CPU, memory, JDBC, JPA, and SQL queries. Ideal for enterprise applications.
3. Java Flight Recorder (JFR) + JDK Mission Control
A tool built into the JVM (available since Java 11). It collects low-level events without significantly impacting performance. Recommended for production environments.
// Recording JFR for 60 secondsjcmd <PID> JFR.start duration=60s filename=recording.jfrPractical Example: Finding a Bottleneck
Suppose we have an application that processes a list of users and generates reports. It runs slowly. Let's write a simple test code and profile it using VisualVM.
Step 1. Code Preparation
import java.util.ArrayList;import java.util.List;
public class SlowApp { public static void main(String[] args) { List<String> users = new ArrayList<>(); for (int i = 0; i < 100000; i++) { users.add("User" + i); } processUsers(users); }
public static void processUsers(List<String> users) { for (String user : users) { String report = generateReport(user); // Simulating saving System.out.println(report.length()); } }
public static String generateReport(String user) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < 1000; i++) { sb.append(user).append(" - data ").append(i).append(""); } return sb.toString(); }}Step 2. Starting Profiling
Run the application and open VisualVM. Select the process in the left panel, go to the "Profiler" tab, and click "CPU" to start profiling.
Step 3. Analyzing Results
After stopping profiling, you will see a table with methods sorted by time spent. In our example, the generateReport method will be at the top — it consumes over 90% of CPU time due to string concatenation in a loop.
Step 4. Optimization
Let's replace StringBuilder with a pre-allocated buffer:
Blogs
Book Recommendations
Read also
call and apply in JavaScript: Top Mistakes and How to Avoid Them
Top Mistakes of Beginner Java Developers: From A to Z
C++: A Language That Has Outlived Eras. Why Is It Still on Top?
Python Developer Interview: How to Ace Your Coding Interview
Working with WebAssembly and Java: Running Java Code in the Browser