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

Introduction: Why Profile Java Code?

\n\n

Developing in Java is not just about writing correct code, but also about creating fast, scalable applications. Even a perfectly designed system can slow down due to a single non-optimal line. How do you find this "bottleneck"? The answer is profiling.

\n\n

Profiling is the process of collecting data about an application's operation: method execution time, memory usage, threads, and CPU load. Without it, optimization becomes guesswork. In this article, we will analyze practical examples of profiling Java code, learn to interpret results, and find bottlenecks.

\n\n

We will look at two popular tools: VisualVM (free, built into the JDK) and JProfiler (commercial, but powerful). All examples will use real code.

\n\n

Main Part: Profiling in Practice

\n\n

1. Setting Up the Environment and Launching the Profiler

\n\n

First, make sure you have JDK (8 or higher) installed. VisualVM comes with JDK 8 and 9; for newer versions, you need to download it separately from the official website.

\n\n

Let's write a simple "slow" code for testing. Create a class SlowApp.java:

\n\n
import java.util.ArrayList;\nimport java.util.List;\nimport java.util.Random;\n\npublic class SlowApp {\n    public static void main(String[] args) throws InterruptedException {\n        List<String> data = new ArrayList<>();\n        Random random = new Random();\n\n        // Simulating long data loading\n        for (int i = 0; i < 100_000; i++) {\n            data.add(generateString(random));\n        }\n\n        // Data processing\n        long sum = 0;\n        for (String s : data) {\n            sum += processString(s);\n        }\n\n        System.out.println("Result: " + sum);\n        Thread.sleep(10000); // To have time to connect with the profiler\n    }\n\n    private static String generateString(Random random) {\n        StringBuilder sb = new StringBuilder(100);\n        for (int i = 0; i < 100; i++) {\n            sb.append((char) ('a' + random.nextInt(26)));\n        }\n        return sb.toString();\n    }\n\n    private static long processString(String s) {\n        long result = 0;\n        for (char c : s.toCharArray()) {\n            result += c;\n            // Artificial delay\n            for (int j = 0; j < 100; j++) {\n                result += Math.sqrt(j);\n            }\n        }\n        return result;\n    }\n}
\n\n

Run the application:

\n
javac SlowApp.java\njava SlowApp
\n\n

While the program is running (it will sleep for 10 seconds), open VisualVM. In the left panel, find your process (SlowApp) and double-click. Go to the Sampler or Profiler tab — you will see CPU and memory statistics.

\n\n

2. CPU Analysis: Finding the Heaviest Method

\n\n

In VisualVM, click the CPU button in the Sampler section. After a few seconds, you will get a table with methods sorted by execution time (Self Time).

\n\n

In our example, you will see that the processString method takes over 80% of the time. Inside it is the nested loop Math.sqrt(j). This is the bottleneck.

\n\n

How to interpret this?

\n
    \n
  • Self Time — time spent directly executing the method (excluding calls to child methods).
  • \n
  • Total Time — total time, including all nested calls.
  • \n
  • Invocations — number of method calls.
  • \n
\n\n

Example output (simplified):

\n
Method                          Self Time (ms)   Invocations\nSlowApp.processString           8500              100000\nSlowApp.generateString          1200              100000\njava.lang.Math.sqrt             6000                

Blogs

Book Recommendations