Profiling Java Code: Finding Bottlenecks in Practice

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

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.exe


2. 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.jfr


Practical 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