Overview

6 Finding hidden issues using profiling techniques

This chapter explains how to uncover hidden performance and behavior issues in Java applications using profiling, emphasizing that profilers are practical, approachable tools rather than last-resort, complex instruments. It focuses on three complementary techniques—sampling to discover what code is running, instrumentation (profiling) to measure execution details like call counts and timings, and JDBC profiling to see the exact SQL sent to a database. The guidance stresses a disciplined workflow: start broad with sampling to find hotspots and execution paths, then narrow down with targeted instrumentation, and finally examine database interactions, all while distinguishing wall-clock time from CPU time to separate “waiting” from “working.”

Sampling provides a fast, low-overhead way to map executing methods and threads, making it invaluable for unfamiliar or opaque codebases, including those that rely on dynamic proxies and frameworks. Through a latency example, the chapter shows how sampling reveals that an endpoint’s 5-second delay is caused by an external HTTP call rather than CPU-heavy work, highlighting the importance of comparing total time with CPU time. Instrumentation then adds precision—counting method invocations and attributing time to specific code regions—so you can confirm whether slowness stems from a single slow call or an inefficient design that repeats calls. The advice includes scoping instrumentation to relevant packages to reduce overhead and using the profiler’s call trees to decide where to debug next.

For database issues, the profiler can intercept JDBC calls to capture full SQL, execution times, counts, and the originating call stacks—crucial for diagnosing slow queries, N+1 problems, and long commits. This works even when frameworks like Hibernate or Spring Data generate queries behind the scenes, enabling you to copy queries into a SQL client, analyze plans, and connect queries back to application code. The chapter contrasts this with logging, which often obscures parameters and origins, and illustrates common pitfalls such as per-row lookups that should be replaced by joins, and criteria API mistakes that inadvertently trigger cross joins. It concludes by recommending profiling as a routine audit step during development and noting that exported profiling data can be further analyzed with AI assistants to accelerate diagnosis and remediation.

The app we are investigating exposes an endpoint: /demo. When you call this endpoint, you must wait 5 seconds for the app to respond. We need to understand why it takes so long for the endpoint to respond. We know our app calls a mock endpoint from httpbin.org, which causes the delay, but we want to learn how to investigate this scenario with a profiler. This way, you’ll know how to use similar techniques for real-world situations.
When the endpoint is called (in this figure, using cURL), the app takes about 5 seconds to respond. In our scenario, we use a profiler to investigate this latency problem.
To start sampling the execution, select the process from the list on the left side, and then select the Sampler tab.
The profiler shows all the active threads in a list. You can expand each item to see the execution stack and an approximate execution time. When the app executes, the newly created threads appear in the list, and you can analyze their execution.
The stack trace shows what the app executes. You can see every method and each subsequent method that is called. This view helps you quickly find the code you want to focus on when investigating a certain capability.
When you expand the execution stack, you find which methods execute and how much time they spend executing. You can also deduce how long they wait and how much they work. The profiler shows both the app’s codebase methods and the methods called from specific dependencies (libraries or frameworks) the app uses.
The framework keeps the implementations for an abstraction separate and provides them dynamically during execution. Because the implementation is decoupled and the app provides it during run time, it’s more difficult to find it by reading the code.
Saving sampled data in VisualVM (top) and JProfiler (bottom). VisualVM allows CPU sampling snapshots, while JProfiler provides options to export profiling data, ensuring easy analysis and sharing of performance metrics.
Example of using an AI assistant to analyze exported profiling data. The AI identifies latency bottlenecks in the /demo endpoint, highlighting issues in the DemoController.demo method and the Feign client for troubleshooting.
Profiling a part of the app during execution to get details about the times a given method was invoked. We can see that the method causing the 5 seconds of latency is invoked only once, meaning the number of invocations doesn’t cause a problem here.
When profiling the app, we observe that one of the methods is called 10 times. We now need to ask ourselves if this is a design issue. Since we now have a big picture of the entire algorithm and we know what code is executed, we can also debug the app if we can’t figure out what happens.
The profiler intercepts the SQL queries the app sends to the DBMS through the JDBC driver. This gives you an easy way to get the queries, run them, observe what part of the codebase runs them, and know how many times a query is executed.
In a Java app, the communication with a relational DBMS is done through the JDBC driver. A profiler can intercept all method calls, including those of the JDBC driver, and retrieve the SQL queries the app sends to a DBMS. You can get the queries and use them in your investigations.
For each query, the profiler also provides the execution stack trace. You can use the stack trace to identify which part of your app’s codebase sent the query.
Even when working with a framework, the profiler can still intercept the SQL queries. This makes your investigation a lot easier because you can’t copy the query directly from the code as you can when using JDBC and native queries.
An AI assistant can be a powerful tool for extracting and refactoring data from logs. In this example, you can see how I use ChatGPT to transform a partial or complex query into a complete, well-structured, and more readable version, making troubleshooting and analysis much easier.
The profiler can intercept any SQL query sent to the DBMS through the JDBC driver. Here, we spot an issue in the generated query—an unneeded cross join that causes performance problems.
By eliminating the supplementary select() method call, the cross join disappeared. However, the overall algorithm for this app should be revised, since it still runs the same query multiple times, which is not optimal.

Summary

  • A profiler intercepts the app’s execution and provides essential details about the code in execution, such as the execution stack trace for each thread, how long it takes for each method to execute, and how many times a certain method was called.
  • When investigating latency problems, the first step to using a profiler is sampling—a way for the profiler to intercept the executing code without getting many details. Sampling is less resource consuming and allows you to observe the big picture of execution.
  • Sampling gives you three essential details:
    • What code executes—When investigating an issue, you sometimes don’t know what part of the code executes, and you can find this aspect by sampling.
    • Total execution time of every method—This detail helps you to identify what part of the code causes potential latency problems.
    • Total CPU execution time—This detail helps you to identify whether your code spends the execution time “working” or waiting for something.
  • Sometimes sampling is enough to understand where a problem comes from. But in many cases, you need more details. You can get these details profiling the execution.
  • Profiling is a resource-consuming process. With a real-world app, it’s almost always impossible to profile the whole codebase. For this reason, when profiling for details, you should filter specific packages and classes on which you want to focus your investigation. You can usually determine what part of the app to focus on by sampling the execution first.
  • An essential detail you get by profiling is the number of invocations of a method. When sampling, you know the total time a method spends executing, but not how often it was called. This aspect is important in identifying a method that is slow or wrongly used.
  • You can use a profiler to get SQL queries the app sends to a DBMS. The profiler intercepts any queries, regardless of the technology used to implement the app’s persistence layer. This is invaluable when investigating slow queries for apps that use frameworks (such as Hibernate) to work with a database.

FAQ

What is “sampling” in a Java profiler, and why should I start with it?Sampling periodically inspects active threads and their stack traces to reveal which methods are executing. It gives a low-overhead, big-picture view of where time is spent, helping you quickly find the area to investigate. Often, this is enough to locate bottlenecks or the right place to set a breakpoint before using heavier profiling.
How is sampling different from instrumentation (profiling)?Sampling provides approximate timings and call stacks but doesn’t count exact invocations. Instrumentation inserts measurement probes to capture precise timings and how many times methods run. Instrumentation offers more detail but adds overhead, so you should scope it carefully to the suspected packages/classes found via sampling.
How do I use VisualVM to sample an app and track down a latency?Steps: - Start the app and ensure the profiler can attach (for remote/secured setups, add the needed JVM options, such as -Djava.rmi.server.hostname=localhost). - Open VisualVM, select the target process, and choose the Sampler tab. - Start CPU sampling, trigger the slow operation, then expand the new/active threads. - Walk the stack to your code and dependency calls; look for methods with the largest total time to find the bottleneck.
What does it mean if a method shows long execution time but near-zero CPU time?It indicates the method is waiting (e.g., on I/O, network, locks) rather than doing CPU work. For example, an HTTP call may block for several seconds while consuming almost no CPU. Fixes typically target the wait source (optimize the remote call, add timeouts, cache results, make it async), not the algorithm.
Can a profiler help when my app uses frameworks, dynamic proxies, or third‑party libraries?Yes. Profilers show full stacks that include framework and library calls (e.g., OpenFeign, Spring components). Even if the concrete implementation is provided via dynamic proxies and isn’t obvious in code, the call tree reveals what actually runs and where time is spent.
When should I switch from sampling to instrumentation, and how do I limit the overhead?Switch after sampling identifies a suspicious area you need to measure precisely (e.g., to count calls). In the profiler, restrict instrumentation to specific packages/classes using include rules (for example: com.example.** for all subpackages, feign.** for a dependency). Avoid profiling the entire app to keep overhead and noise down.
How do I capture and analyze SQL queries my app executes (including ones generated by Hibernate or Spring Data)?Enable JDBC/SQL profiling in the tool. The profiler intercepts calls at the JDBC driver and lists the executed queries and their counts, regardless of whether they were written as native SQL, JPQL, or criteria queries. You can copy queries into a SQL client to analyze plans and performance.
What is the N+1 query problem, and how do I spot it with a profiler?The N+1 problem occurs when the app runs one initial query and then N additional queries—one per retrieved row—causing excessive round-trips and latency. In the JDBC profiling view, look for the same query executed many times. Fix by using proper joins, fetch strategies (eager/fetch joins), batching, or rewriting the retrieval to a single, set-based query.
Can I export profiling data or logs and use AI to speed up analysis?Yes. Many profilers export snapshots (e.g., CSV/XML). You can share them with an AI assistant to summarize hotspots, correlate call trees, or flag repeated queries. For logs, AI can reformat SQL, bind parameters, and generate runnable variants to test different scenarios.
What pitfalls can instrumentation uncover in criteria queries, and how do I fix them?Instrumentation plus JDBC profiling can reveal inefficient or incorrect generated SQL, such as an accidental cross join from calling from() twice in a criteria query. Reuse the existing root (e.g., product) instead of creating a second one, review joins and where clauses, and re-profile to confirm the SQL is corrected and not executed repeatedly.

pro $24.99 per month

  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose one free eBook per month to keep
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime

lite $19.99 per month

  • access to all Manning books, including MEAPs!

team

5, 10 or 20 seats+ for your team - learn more


choose your plan

team

monthly
annual
$49.99
$499.99
only $41.67 per month
  • five seats for your team
  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose another free product every time you renew
  • choose twelve free products per year
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime
  • renews annually, pause or cancel renewal anytime
  • Troubleshooting Java, Second Edition ebook for free
choose your plan

team

monthly
annual
$49.99
$499.99
only $41.67 per month
  • five seats for your team
  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose another free product every time you renew
  • choose twelve free products per year
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime
  • renews annually, pause or cancel renewal anytime
  • Troubleshooting Java, Second Edition ebook for free
choose your plan

team

monthly
annual
$49.99
$499.99
only $41.67 per month
  • five seats for your team
  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose another free product every time you renew
  • choose twelve free products per year
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime
  • renews annually, pause or cancel renewal anytime
  • Troubleshooting Java, Second Edition ebook for free