Async Profiler: Uncover Hidden Performance Issues in Java!

Transcript:

Some profilers are like elephants in a china shop. They are heavy and disruptive. A sync profiler is a ninja - it is small, silent, and incredibly effective. Join me as we profile a Java application with a sync profiler, both locally and in containers.

What is a sync profiler? A sync profiler is a low-overhead open-source profiler for HotSpot JVM-based applications. It is really small and without a graphical user interface, so it can be embedded into other systems, and its work is almost invisible to the application and has almost no effect on performance. But don't be misled by its miniature size - it's really powerful. A sync profiler can collect data on various JVM events: CPU usage, allocation, methods, and so on.

But how does it differ from other open-source profilers? Well, it can monitor non-Java threads, native calls, and kernel functions. And you know what? You don't need elaborate setup to use this profiler - just one command to attach it to the application and that's it. You will get profiling data as a JFR file or a flame graph. Of course, there are a couple of additional steps to use it with containers, but don't worry - you'll get a grip on it really quickly, especially after I show you how to work with this tool.

Setting up async profiler Async profiler is available for macOS and Linux. You can get the necessary package on GitHub. Locally, I'm using macOS, so I've got this build. After you unpack the archive, you will see that there are two directories: bin contains asprof that attaches and controls the agent to the running process. lib contains the profiler library that can be attached to the application as a Java agent.

Profiling a Java application locally Right, so let's first look at how we can attach to the running Java process. Start any Java application - I'm using reference Spring Boot TP Clinic. We need a PID of the process, and the easiest way is to use jps. So here it is. Instead of PID, you can use the name of the application, so you can simply use asprof to start and stop the application. Right, so you can say asprof start and PID or name of the application, and then after you're done or after some period of time, you can say asprof stop and PID.

But of course, there are additional options to configure the profiling session. So let's do that. Right, so again asprof, and you can set the event with the flag -e, for instance -e cpu. You can set the duration with -d, let's set it to 30 seconds. And you can also specify the name and the extension of the file with -f. So let's call it profile.html.

HTML means that we will get a flame graph. But of course, if you want a JFR file, the extension will be .jfr. There are lots of other options for the profiler, right? You can find them in the documentation. So there's start, stop, and then resume, dump, and so on - options for any format (HTML or JFR). Then there are options only for JFR files, and options only for flame graph files. You can profile several events, but in this case, you can get only the JFR file.

Alright, so the profiling has started. Let's just push some buttons while we wait, so it's not just staring into the screen. Right, so the profiling is done and you can now open the file in the browser. Right, so that's our flame graph. That's the color-coded representation of all the samples that profiler took. And of course, you can inspect each call in more detail. You can also attach async profiler at application start and the profiling will start at once with the application. This way you can get valuable information about application behavior when it starts. So for that purpose, we need to attach async profiler as a Java agent.

We remember that in the lib folder we have this agent libasyncProfiler.dylib in case of macOS and .so in case of Linux. If you use Linux. So let's copy the full path to the agent.Now we need to attach this agent and we are going to use the JAR file. I have already prepared one. So -javaagent:path - that's the option that we need. Then here you specify the full path to the agent. Then you say start and here you can also specify various options like events - so again, let's specify cpu. You can specify the file - right, so it's going to be again profile.html.

You can of course use other options, but for the demonstration, that's enough.

Then next we say -jar and point to our JAR file, which is in the target folder. That's basically it. So when you start the JAR file, the profiling will start. Here it is - you can see that profiling has started. Okay, and then you can stop the application and the file will be in the project directory. You can experiment with profiling modes and settings later on your own.

Profiling Java applications in containers. To profile an application with async profiler from a container, you need to include it into the image. It is really easy to do with Alpine Linux because you can simply install the async profiler package from the Alpine repository. We will need this Dockerfile. During the first stage, we specify the image that we're going to use as a builder. I'm using the Liberica Runtime container - it is based on Liberica JDK and Alpine Linux.

Right, so first stage is super obvious - we simply package our application into a JAR file. Then on the second stage, we take Liberica Runtime container with JRE (because we don't need JDK). Here the important thing is that we run apk add async-profiler, right - we are adding the async profiler package. Then you just specify the entry point.

Alright, so again, we need to specify the absolute path to the agent - async profiler. The profiler will be in the /opt folder - /opt/async-profiler/lib/asyncProfiler.so, right. So as I said, that's the extension for Linux. This package also contains asprof, so we get both the agent and the tool that controls it - this is very useful. I'll show it to you later. Of course, you can specify the necessary options - right, so start the profiler with the application. Let's specify the event and file - it's going to be saved into the /tmp folder of the container. Then you just run the JAR file. Great. But before we run the container image, we need to configure the Linux kernel to allow access to the performance event. For that purpose, we need to set two options: sudo sysctl kernel.perf_event_paranoid=1 sudo sysctl kernel.kptr_restrict=0 I have already enabled these options, so I'm not going to do that. But you have to.

These options are available for Linux only and are necessary in the container - otherwise, the profiling data won't be complete. For macOS, you might not need them, because we are profiling containers and there's Linux in containers usually. Before we go any further, it's important to know that Docker containers restrict access to performance events. So you can use one or several of the following approaches: Add the capability SYS_ADMIN to access privileged performance event info Disable the default seccomp profile with --security-opt seccomp=unconfined Use FD transfer with privileged container Fall back to cstack timer mode which doesn't need perf events Taking that into consideration, the command for running the container may look like this: docker run -p 8080:8080 --name pet \ --cap-add=SYS_ADMIN \ --security-opt seccomp=unconfined \ your-image-name The profiling has started. Success!

How do we get the profiling data? The file profile.html will be created automatically in the /tmp directory when you stop the container. But what if you don't want to stop a running container (e.g., in production)? In this case, you can stop the profiling session manually. Let's enter the container shell: docker exec -it pet /bin/sh We are inside. I already said the async profiler package includes asprof.

We can use it to stop the profiling session, or resume/dump data without stopping the session. Let's find the PID of the process - usually it's 1, but let's be sure: ps Yes, it's 1. Now use asprof: asprof dump -f /tmp/profile.html -p 1 That's it. Now exit the container. To copy the file: docker ps -a docker cp :/tmp/profile.html . Success! That's your file. Buildpacks If you use Buildpacks, there are some considerations for using async profiler. First: async profiler requires the libstdc++ library. Alpine includes it, but Buildpacks (like Spring Boot 3.4's tiny builder) do not. So, you should change the builder to paketobuildpacks/builder-jammy-base. Add this config to spring-boot-maven-plugin: paketobuildpacks/builder-jammy-base

Also, Buildpacks create layered jars. Extra files outside standard layout will be discarded. Solution: put async profiler in the resources directory. Rename the profiler lib to just profiler.so, put it in resources, and remember that it ends up under: /workspace/BOOT-INF/classes/ Add the JVM option: BP_JAVA_OPTS="-javaagent:/workspace/BOOT-INF/classes/profiler.so=start,event=cpu,file=/tmp/profile.html" Then build the image: ./mvnw spring-boot:build-image Now run the container like before. Catch: If you enter the container and try to stop profiling with asprof, it won't work - permission denied. Because the container runs as non-root, and this can't be changed. So you need to stop the container and then extract the file with docker cp. But there's another way - profiling from the host You don't need to include the profiler in the container. You just bind-mount it and use it externally. docker run -d -p 8080:8080 --name pet \ --cap-add=SYS_ADMIN \ -v /absolute/path/to/profiler:/profiler \ your-image-name Then, on the host: docker top pet sudo /profiler/asprof -e cpu -d 15 -f /tmp/profile.html -p Once done: docker ps docker cp :/tmp/profile.html . There it is - your profiling file. Async profiler is small, powerful, and easy to use. Try it with your application. Code snippets are available in the article linked in the description.

Don't forget to like, subscribe, and see you next time!

Summary

In this video, Async Profiler is used to analyze the performance of Java applications both locally and in containers. The setup process is demonstrated for macOS and Linux, along with instructions for attaching the profiler at runtime or application startup. The video covers profiling in Docker containers, including necessary kernel configurations and container security options. It also explains how to use Async Profiler with Buildpacks by embedding the profiler into the container image. Additionally, profiling from the host without modifying the container is shown as an alternative method. The result is a flame graph or JFR file that helps visualize performance bottlenecks.

About Catherine

Java developer passionate about Spring Boot. Writer. Developer Advocate at BellSoft

Social Media

Videos
card image
Mar 9, 2026
jOOQ Deep Dive: CTE, MULTISET, and SQL Pipelines

Some backend developers reach the point where the ORM stops being helpful. Complex joins, nested result graphs, or CTE pipelines quickly push frameworks like Hibernate to their limits. And when that happens, teams often end up writing fragile raw SQL strings or fighting performance issues like the classic N+1 query problem. In this video, we build a healthcare scheduling application NeonCare using jOOQ, Spring Boot 4, and PostgreSQL, and show how to write production-grade SQL directly in Java while keeping full compile-time type safety.

Videos
card image
Feb 27, 2026
Spring Developer Roadmap 2026: What You Need to Know

Spring Boot is powerful. But knowing the framework isn’t the same as understanding backend engineering. In this video, I walk through the roadmap I believe matters for a Spring developer in 2026. We start with data. That means real SQL — CTEs, window functions, normalization trade-offs — and understanding what ACID and BASE actually imply for system guarantees. Spring Data JPA is useful, but you still need to know what happens underneath. Then architecture: microservices vs modular monolith, serverless, CQRS, and when HTTP, gRPC, Kafka, or WebSockets make sense. Not as buzzwords — but as design choices with trade-offs. Security and infrastructure follow: OWASP Top 10, AuthN vs AuthZ, encryption in transit and at rest, Docker, Kubernetes, Infrastructure as Code, and observability with Micrometer, OpenTelemetry, and Grafana. This roadmap isn’t about mastering every tool. It’s about knowing what affects reliability in production.

Further watching

Videos
card image
Apr 2, 2026
Java Memory Options You Need in Production

JVM memory tuning can be tricky. Teams increase -Xmx and assume the problem is solved. Then the app still hits OOM. Because maximum heap size is not the only thing that affects memory footprint. The JVM uses RAM for much more than heap: metaspace, thread stacks, JIT/code cache, direct buffers, and native allocations. That’s why your process can run out of memory while heap still looks “fine”. In this video, we break down how JVM memory actually works and how to control it with a minimal, production-safe set of flags. We cover heap sizing (-Xms, -Xmx), dynamic resizing, direct memory (-XX:MaxDirectMemorySize), and total RAM limits (-XX:MaxRAMPercentage) — especially in containerized environments like Docker and Kubernetes. We also explain GC choices such as G1, ZGC, and Shenandoah, when defaults are enough, and why GC logging (-Xlog:gc*) is mandatory before tuning. Finally, we show how to diagnose failures with heap dumps and OOM hooks. This is not about adding more flags. It’s about understanding what actually consumes memory — and making decisions you can justify in production.

Videos
card image
Mar 26, 2026
Java Developer Roadmap 2026: From Basics to Production

Most Java roadmaps teach tools. This one teaches order — the only thing that actually gets you to production. You don’t need to learn everything. You need to learn the right things, in the right sequence. In this video, we break down a practical Java developer roadmap for 2026 — from syntax and OOP to Spring, databases, testing, and deployment. Structured into 8 levels, it shows how real engineers grow from fundamentals to production-ready systems. We cover what to learn and what to ignore: core Java, collections, streams, build tools, Git, SQL and JDBC before Hibernate, the Spring ecosystem, testing with JUnit, and deployment with Docker and CI/CD. You’ll also understand why most developers get stuck — jumping into frameworks too early, skipping SQL, or treating tools as knowledge. This roadmap gives you a clear path into real-world Java development — with priorities, trade-offs, and production context.

Videos
card image
Mar 19, 2026
TOP-5 Lightweight Linux Distributions for Containers

In this video, we compare five lightweight Linux distributions commonly used as base images: Alpine, Alpaquita, Chiseled Ubuntu, RHEL UBI Micro, and Wolfi. There are no rankings or recommendations — just a structured look at how these distros differ so you can evaluate them in your own context.