Java applications can be extremely performant thanks to the inner workings of JVM and dynamic performance optimization. If the SLAs are met and cloud costs are optimal, you don’t have to do anything; premature optimization is the root of all evil, as the saying goes.
But there might come the time when the SLAs are broken or cloud expenses have gone through the roof. The application may suffer from memory leaks, eat away at CPU resources, and simply “work too slow.” Identifying the root causes of performance issues is impossible without application profiling.
This article will guide you through key concepts and areas of profiling, describe the most popular Java profilers, and outline the best practices for efficient profiling.
Table of Contents
What is Java profiling?
Java profiling is the process of analyzing application behavior running on Java Virtual Machine at runtime. It helps gain insights into memory allocation, garbage collection, thread behavior and other JVM operations. Profiling helps to identify performance bottlenecks, gain understanding of how application behaves under load and stress testing, and find code areas requiring optimization.
Profiling is an essential procedure for pinpointing the root causes of deteriorated performance of your Java applications. It may also be required if you are adding a critical feature to your application. Profiling usually deals with low-level processes such as CPU and memory usage; therefore, more high-level performance metrics such latency are out of its scope.
There are two types of profilers: instrumenting and sampling ones. Instrumenting profilers modify the application’s code by inserting method invocation logging. This approach is easier to implement, for some basic measurements developers don ’t even need specific tools. The downside is that instrumenting provides limited information about the code behavior plus introduces serious performance overhead.
Sampling profilers periodically (e.g., every 10 ms) receive from JVM the stack trace of each thread running at that point. Then, the profiler aggregates samples and provides a general picture of application execution. This approach is associated with smaller overhead, but it can yield inaccurate results depending on several factors, including how often, for how long, and at which points the profiler took the samples.
It is possible to collect samples continuously in the background while the application is running in production. However, in such cases it is crucial to use profiling tools with a minimal overhead.
Key areas of Java profiling
What are the key areas of Java profiling? Then can be allocated into four main categories:
- Execution profiling looks into how much time each application method is consuming. You can evaluate CPU time or wall-clock time;
- Memory profiling examines the memory usage by application objects and garbage collection behavior, identifies memory leaks. One of the most common types of memory profiling is allocations profiling that tracks memory allocation events;
- Thread profiling is aimed at analyzing thread lifecycle contention, and synchronization, helping to identify thread locking issues;
- I/O profiling deals with evaluating the speed of read/write operations.
Some modern profilers offer additional functionality such as profiling of database queries and web requests.
Top Java profiling tools
Below is the list of popular Java profilers. The list is by no means exhaustive, there are many other tools offering similar and distinctive functionality, both open source and commercial. Describing each existing profiler would be too much for an article, so I have decided to go with the most common, established profilers plus a couple of novel interesting solutions.
Free and open source Java profilers
JFR + JDK Mission Control
JDK Flight Recorder or Java Flight Recorder (JFR) is a low-overhead profiler built into OpenJDK, which collects data about various JVM events. JFR can record hundreds of event types, plus you can create custom events to suit your needs. JFR recordings are binary log files, which can be analyzed in JDK Mission Control, a set of monitoring and profiling tools with a GUI.
JDK Mission Control (JMC) is also free and open-source, but it is not included in the OpenJDK distributions, and has to be downloaded separately from vendors that provide it. For instance, you can get Liberica Mission Control for Windows, macOS x86/ARM, or Linux.
You can use JDK Mission Control on its own to analyze JVM processes running locally or remotely:
But the true power lies in the combination of JDK Mission Control and JFR. JMC offers multiple reports for analyzing various JVM events:
In addition, you can create your own filters to customize built-in reports.
Back to JFR. There’s no need to download anything apart from a Java runtime, as OpenJDK distributions already include it. You can enable JFR upon Java application start:
java -XX:+UnlockDiagnosticVMOptions \
-XX:+DebugNonSafepoints \
-XX:StartFlightRecording=duration=30s,filename=my-recording.jfr -jar app.jar
The -XX:+DebugNonSafepoints
flag enables JFR to collect the information outside the safepoints. A safepoint is when the state of the executing thread is well described.
Alternatively, you can use the jcmd tool to start and stop the recording:
jcmd PID JFR.start
jcmd PID JFR.dump filename=my-recording.jfr
jcmd PID JFR.stop
Finally, you can take advantage of continuous flight recording. As JFR is associated with low overhead, you can let it run silently in the background. In the simplest scenario, simply change the -XX:StartFlightRecording
flag arguments to
-XX:StartFlightRecording=name=background,maxsize=100m
Where maxsize is the limit to the events stored in memory. If you’d like to know more about working with JFR and Mission Control, refer to our series of guides on Java profiling.
VisualVM
VisualVM is an open-source Java profiler with a graphic interface that uses the JMX API to collect the data. It was first integrated into JDK 6, but later removed from JDK 9. So, you have to download and use it as a standalone tool now.
Using VisualVM is pretty straightforward: simply choose a running JVM process on the left and browse the relevant stats in real-time. You can also connect to the remote JVM process. And you can also take an application snapshot and analyze it later.
Basic VisualVM functionality includes monitoring CPU and memory usage, garbage collection, running threads, and heap. You can extend this functionality by using ready plugins or even write your own plugin. For instance, there's a startup plugin that enables you to analyze application performance right from the startup.
Async Profiler
Async Profiler is an open-source low-overhead profiler for OpenJDK runtimes and other HotSpot JVM-based runtimes. Originally, it used only the internal AsyncGetCallTrace API to collect the data about CPU usage, heap allocation, methods, hardware and software performance counters.
The AsyncGetCallTrace API enables the profiler to collect the data about frames of a thread outside the safepoint. However, it is an internal ‘unofficial’ API and it only returns information about Java frames. So, a new stack walker that doesn’t depend on the AsyncGetCallTrace API was implemented. It can be enabled with --cstack=vm and is supported on Linux x64 and Linux Aarch64 with HotSpot JVM.
The Async Profiler builds are available for Linux and macOS. The profiler is really small, and there’s no GUI, so you can use it as a standalone CLI tool or embed it into another solution.
Using it is simple. You can profile a running application:
asprof -e cpu -d 30 -f profile.html PID
Or attach the profiler as a Java agent upon application startup:
java -agentpath:/path/to/libasyncProfiler.so=start,event=cpu,file=profile.html -jar app.jar
The profiler can produce flame graphs and JFR files.
Java profilers with freemium licenses
New Relic
New Relic is not only a profiler, but an observability platform that offers a wide variety of features. Profiling is only part of the pack that also includes anomalies alerts, infrastructure monitoring, security testing, and more. The available services depend on the Subscription Plan. The solution provides multiple dashboards that visualize your data in real-time or post factum.
Once you have completed the installation process, you can attach the New Relic agent to the process just like any other profiler:
java -javaagent:/full/path/to/newrelic.jar -jar app.jar
New Relic offers paid plans for enterprises and a free plan for one basic user and 100 GB of data ingest.
Digma.ai
Digma differs from the profilers described above in a way that it continuously observes the running code and then analyzes it to produce insights into bottlenecks, code smells, and other performance issues related to the code. It can detect slow DB queries, for instance, or N+1 problems. In addition, it tracks CPU and heap usage, but its main focus is on the code.
Digma uses OpenTelemetry to collect the data. It integrates into Intellij IDEA as a plugin and can be used for free if deployed locally or with a paid license if connected to the central environment.
Using Digma is easy: simply install the plugin, then follow the instructions to install the Analytics Engine. After that, when you run your code or tests, Digma starts to observe it in the background, producing relevant information about detected issues, which you can analyze without leaving the IDE.
Commercial Java profilers
JProfiler
JProfiler is a robust Java profiler with a UI that offers a comprehensive selection of features including:
- CPU and memory profiling, a heap walker for identifying memory leaks;
- Thread profiling;
- Database calls profiling with support for JDBC and JPA;
- HTTP calls profiling.
In addition, JProfiler can be used for real-time monitoring. The profiler integrates with IDEs as a plugin plus offers facilitated access to JVMs running in Kubernetes or Docker.
You have to purchase a license to use JProfiler, but there’s also a free 10-day trial so that you can play around with the tool and see if acquiring a license is worth it.
YourKit
YourKit offers various profiling features including CPU, memory, GC, database queries, and thread profiling. It can be used to profile local or remote applications and is available as a plugin for several IDEs. YourKit has a UI but can also be used as a command-line tool. The data can be exported into various formats, and support for Open API enables the developers to create custom probes to collect specific data.
YourKit requires acquiring a license for development teams, but the vendor also offers free licenses for open-source projects and licenses at lower cost for educational and scientific organization. There’s a 15-day free trial to try out all the profiler's features.
IDE plugins for profiling
IntelliJ Profiler
IntelliJ Profiler uses Async Profiler under the hood and is a part of IntelliJ IDEA Ultimate. As it is an integral part of IDE, there’s no need to set up anything, and one can attach IntelliJ Profiler to the running application with a single click. After that, you can inspect the data produced by the profiler in an output window. The collected data includes total CPU time in the form of a flame graph, method call tree, and JVM events.
Best practices of Java profiling
Profilers equip the developers with a handful of valuable insights and plenty of data. Not knowing how to treat this data or how to work with profilers renders the whole procedure useless. Below are some key recommendations for smooth and fruitful profiling of your applications:
- Understand the profiling results. This piece of advice may seem like stating the obvious, but even experienced developers may get lost in the unchartered waters of profiling data. Learn how to analyze profiling data, how to identify performance issues and which parts of the application are to blame.
- Identify Key Performance Indicators (KPIs). Choose the areas that are most important to optimize plus metrics that will be most optimal for each area. After that, use this data as a reference when studying the profiling data.
- Don’t over-optimize. Profilers may identify multiple issues, some less critical, and some significantly affecting the application performance. Perfection is impossible, so don’t waste your time on fine-tuning everything. Check against the established KPIs and solve the problems that prevent the team from reaching them.
- Don’t treat the profiling data as gospel. The profiling data is not always precise; it depends on numerous factors including the length of profiling, frequency of sampling, when the sampling happens, and even the overhead of using the profiler. One way to increase the data reliability is to select the low-overhead profiler, understand its modus operandi, and adjust the period and frequency of sampling.
- Profile the application under load. Profiling the application in vitro (i.e., on a local machine under minimal load) won’t yield useful results. Performs load testing and stress testing to understand how your application behaves under different loads. In addition, profile the application in real-life to get even more sensible data (see the section below).
- Consider the production-like environment. Your application is likely to run in a containerized environment, where it may behave differently. Perform profiling in Docker, Kubernetes, or wherever the application is deployed to in production.
Conclusion
Profiling is an indispensable part of application development. By choosing a right profiler that best suits your needs and setup and following profiling best practices you will be able to acquire valuable data about the behavior of your Java application. And then, you can leverage this information to optimize the application and JVM settings to meet the demands of your users and efficiently use the resources.
Subscribe to our newsletter for more articles on Java development and performance optimizations!