In modern software applications, especially large and long-running Java programs, memory management is critical.
Poor memory usage can lead to performance degradation, OutOfMemoryErrors, and even application crashes.
Heap profiling — the process of analyzing memory usage patterns — helps developers understand, optimize, and debug how memory is allocated and used.
However, traditional heap profiling tools often introduce heavy overhead (slowing down the application), making them impractical for use in production systems.
To address this, Java (starting from JDK 11+) introduced Low-Overhead Heap Profiling, allowing efficient memory analysis with minimal impact on application performance.
What is Heap Profiling?
Heap Profiling is a technique where:
- Memory allocations are tracked,
- Object types and allocation sites (where in the code memory is allocated) are recorded,
- Developers can analyze which parts of the application consume the most memory.
Heap profiling is essential for:
- Detecting memory leaks,
- Understanding memory growth,
- Optimizing resource usage,
- Improving application scalability.
Traditional profiling tools like VisualVM or external profilers can be heavy, intrusive, and unsuitable for live, production systems.
What is Low-Overhead Heap Profiling?
Low-Overhead Heap Profiling is a feature in Java Flight Recorder (JFR) that provides:
- Sampling-based heap profiling
- Minimal performance impact (typically <2%)
- Continuous memory allocation tracking without needing invasive instrumentation
Instead of tracking every single object allocation (which is costly), sampling means that only a small fraction of allocations are recorded, but they are enough to produce statistically meaningful insights.
How It Works
- Sampling: Not every object allocation is recorded.
Instead, Java records samples based on object size, time, or other thresholds. - Stack Trace Recording: Along with the allocation event, the call stack at the point of allocation is captured.
- Event Recording: Java Flight Recorder (JFR) records these lightweight events to a file.
- Analysis: Developers use tools (like Mission Control) to analyze memory usage patterns later.
You can control:
- Sample frequency
- Maximum memory used for recording
- Which allocations are recorded
This ensures extremely low runtime overhead and minimal memory consumption for profiling itself.
Sample Implementation
import java.util.ArrayList;
import java.util.List;
public class MemoryConsumer {
public static void main(String[] args) throws InterruptedException {
// List to store allocated objects
List<byte[]> memoryHog = new ArrayList<>();
// Simulate memory allocation (1MB arrays)
for (int i = 0; i < 1000; i++) {
memoryHog.add(new byte[1024 * 1024]); // Allocate 1MB arrays
Thread.sleep(100); // Sleep to simulate delay between allocations
}
// Output to indicate the program has finished
System.out.println("Finished allocating memory.");
}
}
Code language: JavaScript (javascript)
Running the Program with JFR:
When running this program, we need to start Java with JFR enabled to record heap allocation samples. This is done using the -XX:+FlightRecorder
and -XX:StartFlightRecording
options.
java -XX:+FlightRecorder -XX:StartFlightRecording=duration=60s,filename=heap-profile.jfr,settings=profile -XX:+UnlockCommercialFeatures -XX:+UnlockDiagnosticVMOptions -XX:+DebugNonSafepoints -jar MemoryConsumer.jar
Explanation of Command:
-XX:+FlightRecorder
enables Java Flight Recorder.-XX:StartFlightRecording=duration=60s,filename=heap-profile.jfr
starts a flight recording for 60 seconds and saves the data to a file namedheap-profile.jfr
.settings=profile
ensures that JFR collects heap allocation events.-XX:+UnlockCommercialFeatures
and other options are for enabling advanced diagnostic features (not required in OpenJDK 11 and later).
When you run the program, you’ll see this message in the console after memory allocations:
Finished allocating memory.
After Running the Program: Analyzing the JFR File
Once the program finishes, a .jfr
file is generated (in this case, heap-profile.jfr
). This file contains the heap allocation data, which can be analyzed using Java Mission Control (JMC).
Here’s what you should do to analyze the heap profiling data:
- Open Java Mission Control.
- Click on File → Open and select the
heap-profile.jfr
file. - Navigate to the Memory section, where you’ll see allocation details for objects.
- It will show which class types consumed the most memory.
- Â
Example Insights from JMC:
- You might find that the
byte[]
class is allocating most of the memory (because the program is adding 1MB byte arrays). - JMC will show you how many times this allocation occurred, where in the code it happened, and how the memory usage evolved over time.
In large systems where memory leaks are difficult to detect, such profiling can be invaluable. By using low-overhead heap profiling, you can continuously monitor memory usage with minimal impact on the running application, ensuring that memory issues are caught early and efficiently.