Java
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented Java
-
Section 4: Collections Framework
-
Section 5: Exception Handling
-
Section 6: Generics
-
Section 7: Functional Java
-
Section 8: Concurrency
-
Section 9: I/O and NIO
-
Section 10: JVM Internals
-
Section 11: Modern Java Features
-
Section 12: Build Tools and Project Structure
-
Section 13: Testing
-
Section 14: Databases and Persistence
-
Section 15: Networking
-
Section 16: Design and Best Practices
-
Section 17: Reflection and Annotations
-
Section 18: Logging and Diagnostics
-
Section 19: Date, Time, and Internationalization
-
Section 20: Java Platform Module System
-
Section 21: Security in Java
-
Section 22: Advanced Collections and Data Structures
-
Section 23: More Concurrency Patterns
-
Section 24: Compression, Files, and System Integration
-
Section 25: GUI Programming
-
Section 26: Practical Projects
-
Section 27: Data Structures and Algorithms
-
Section 28: Interview and Algorithm Practice
-
Section 29: JSON and Data Interchange
-
Section 30: More Concurrency Utilities
-
Section 31: More Collections and Streams Practice
-
Section 32: More File and System Programming
-
Section 33: Standard Library Deep Dive
-
Section 34: More Practice and Drills
-
Section 35: More Testing and Quality
-
Section 36: More Design Patterns and Architecture
-
Section 37: Career and Ecosystem
-
Section 38: More OOP and Architecture Practice
-
Section 39: More Enterprise Concepts
-
Section 40: Advanced JavaFX
-
Section 41: More Interview Practice
172: Profiling with JFR and VisualVM
I've seen this happen dozens of times: a developer comes to me saying, "My app is slow, and I can see in Task Manager that Java is eating 90% of my CPU, so I just need to optimize my loops."
Stop Guessing with the OS Activity Monitor
The problem with relying on your OS's activity monitor is that it tells you that the JVM is working hard, but it tells you absolutely nothing about why. Is your business logic inefficient? Is the Garbage Collector (GC) thrashing because you've run out of heap space? Or are you hitting a synchronized block that's causing a massive thread bottleneck? When you see 90% CPU usage, you're looking at the symptom, not the disease. If you start optimizing loops blindly, you're just guessing, and in a complex system, guessing is a great way to waste three days of your life.
To actually fix performance, you need to move from "outside-in" monitoring (the OS) to "inside-out" profiling (the JVM). This is where Java Flight Recorder (JFR) and VisualVM come in.
Letting JFR Record the Crime Scene
Think of JFR as the "black box" flight recorder on an airplane. It's built directly into the JVM, which means it has incredibly low overhead—usually less than 1%. I often leave it running in production environments because it allows me to diagnose a spike that happened at 3:00 AM without having to try and "reproduce" it locally (which is usually impossible).
JFR doesn't just sample the CPU; it records specific events. For example, if you're dealing with a service that processes large XML files and suddenly slows down, JFR can tell you exactly how many TLAB (Thread Local Allocation Buffer) allocations are happening and which methods are triggering the most GC pressure.
# Start your app with JFR enabled
java -XX:StartFlightRecording=duration=60s,filename=my_recording.jfr -jar app.jar
In that one command, I've told the JVM to record everything for 60 seconds and dump it into a file. I don't have to change a single line of code. You can then open this .jfr file in JDK Mission Control (JMC) to see a timeline of exactly what happened.
Using VisualVM to Perform the Autopsy
While JFR is great for "what happened in the past," VisualVM is my go-to for "what is happening right now." It's a visual tool that connects to a running JVM via JMX (Java Management Extensions).
Imagine you have a caching layer in your app that's supposed to keep the last 1,000 users in memory, but your memory usage is climbing linearly. In VisualVM, you can hit the "Heap Dump" button. This freezes the JVM for a second and takes a snapshot of every single object on the heap. I can then sort by "Retained Size" and instantly see that a HashMap in my UserCache class is actually holding 500,000 entries because someone forgot to implement the eviction logic.
You can also use the "Sampler" tab. Unlike JFR, which is a recording, the Sampler lets you watch the CPU hotspots in real-time. If you see a method like java.util.regex.Pattern.match taking up 40% of your CPU, you know exactly where to look: you're probably compiling a regex inside a loop instead of reusing a static Pattern object.
- Use JFR when: You need production-grade data, you're hunting a transient bug, or you can't attach a debugger to the process.
- Use VisualVM when: You're in development, you suspect a memory leak, or you want to see the immediate impact of a code change on heap usage.
📋 Practical Task
Exercise: Hunting the Memory Leak in the CSV-to-DB Pipeline
You have been given a simplified version of a data ingestion service. The service reads "customer records" from a simulated CSV stream and processes them. However, the application crashes with an OutOfMemoryError after processing about 100,000 records, even though the heap size is plenty for the actual data being processed.
Your Task:
- Run the provided
IngestionService.java. - Launch VisualVM and connect to the running
IngestionServiceprocess. - Use the Sampler or Monitor tab to observe the heap growth.
- Take a Heap Dump once memory usage exceeds 200MB.
- Analyze the heap dump to find the specific collection (List, Map, or Set) that is growing unbounded.
- Identify the line of code causing the leak (Hint: Look for a static collection used for "logging" or "tracking" that is never cleared) and fix it so the memory usage stabilizes.
// IngestionService.java (Snippet)
public class IngestionService {
private static final List<String> processedIds = new ArrayList<>();
public void processRecord(String record) {
String id = record.split(",")[0];
// Business logic here...
doWork(record);
// The leak is hidden here
processedIds.add(id);
}
private void doWork(String r) { /* simulated work */ }
}
There are no comments for now.