Introduction: Why Profile Java Code?
\n\nDeveloping 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\nProfiling 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\nWe will look at two popular tools: VisualVM (free, built into the JDK) and JProfiler (commercial, but powerful). All examples will use real code.
\n\nMain Part: Profiling in Practice
\n\n1. Setting Up the Environment and Launching the Profiler
\n\nFirst, 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\nLet's write a simple "slow" code for testing. Create a class SlowApp.java:
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\nRun the application:
\njavac SlowApp.java\njava SlowApp\n\nWhile 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\n2. CPU Analysis: Finding the Heaviest Method
\n\nIn 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\nIn 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.
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
Example output (simplified):
\nMethod Self Time (ms) Invocations\nSlowApp.processString 8500 100000\nSlowApp.generateString 1200 100000\njava.lang.Math.sqrt 6000