DEV Community

Abhi
Abhi

Posted on

I Got Tired of Guessing JVM Performance — So I Built a Java Agent From Scratch 🚀

I used to do what most backend developers do. When a Spring Boot app felt slow, I’d:

  • 😵‍💫 Stare at logs until my eyes blurred.
  • ⏱️ Add manual System.currentTimeMillis() timers everywhere.
  • ☁️ Blame the database (it's always the DB, right?).
  • 🙏 And just hope the JVM was behaving.

Deep down, I knew the truth: I didn’t actually know what the JVM was doing.

Instead of reading another "Top 10 Performance Tips" blog, I decided to do something uncomfortable. I built a JVM agent from scratch. This is the journey of vtracer.


The Starting Point: Brutal Silence 🧩

Before this, terms like Instrumentation API and Bytecode transformation were just abstract concepts. When I started, I expected clear error messages. I was wrong.

At one point, the JVM simply refused to load my agent. No errors. No stack traces. Just silence. That was Lesson #1: The JVM doesn’t owe you an explanation. If you mess up your bytecode transformer or a manifest entry, the JVM doesn't crash—it just ignores you and moves on. It’s a humbling experience to realize you're invisible to the very system you're trying to track.


The 6-Day Build Journey 🛠️

Day 1: Breaking the Seal

My only goal: Can I make the JVM acknowledge I exist? I wrote a premain method and finally saw a log line appear inside a target JVM. It felt like cracking open a sealed black box. I finally had an Instrumentation handle.

Day 2: The "Explosion" of Reality 💥

I used ByteBuddy to intercept method entry and exit.

Immediately, lesson #2 hit: Tracing everything is a disaster. My console became unusable—thousands of lines per second, a blur of text. The app was technically "working," but it was completely unobservable.

The Takeaway: Real JVM tooling is about restraint, not power. If you can't filter the noise, you're just adding to the chaos.

Day 3: Attaching to Raw Metal (No Restarts) 💉

Using the Attach API, I built a tool to find a running JVM by PID and inject the agent at runtime. No restart. No redeploy.

When I attached it to a live Spring Boot app and saw Tomcat internals like Http11Processor.recycle() executing in real-time, it hit me: This is the exact same mechanism used by million-dollar APMs—just without the marketing and the shiny UI. I was finally touching the raw metal of the ecosystem.

Day 4: Virtual Threads Aren't Magic 🪄

Java 21's Virtual Threads have massive potential, but they also have massive footguns. I wired a JFR RecordingStream to listen for jdk.VirtualThreadPinned events.

I wrote intentionally bad code (a synchronized block inside a virtual thread), and the agent caught it instantly. Virtual threads don’t fix bad blocking—they expose it.

Day 5: The Art of Sampling 📉

Tracing 100% of calls is irresponsible. I implemented Sampling (10% rate). This forced me to think like a systems engineer: predictable overhead, controlled allocations, and finding the "useful signal" in a sea of data.

Day 6: Structured Reporting

I added a Shutdown Hook and JSON output. Now, the agent leaves behind a structured report you can actually analyze, rather than a wall of text you have to scroll past.


What vtracer is Today 📍

  • Runtime Attachment: No restarts required.
  • Dynamic Instrumentation: Power of ByteBuddy.
  • Smart Sampling: Minimal overhead (~2%).
  • JFR Integration: Detecting Virtual Thread pinning.
  • Structured JSON: Professional report generation.

What vtracer will NEVER be:

I’m not building a UI-heavy dashboard or an "AI-powered oracle." vtracer exists to understand the JVM, not to hide it.


Why This Project Matters 💡

This project didn't just teach me APIs. It taught me how fragile instrumentation can be and how much is happening below the application code that we take for granted.

The Reality Check:

If you’ve never attached to a live JVM, you don’t really know Java—you know frameworks.

Building this agent permanently changed how I debug, how I read stack traces, and how I think about performance. This project didn’t necessarily make me a "faster" developer.

It made me more honest about what I don’t know.


🔗 The Code

If you want to stop guessing and start observing, check the source:

👉 GitHub: abhishek-mule/vtracer

  • Java Version: 21+
  • Status: Early, experimental, and very real.

Learning the JVM, one uncomfortable problem at a time.

Top comments (0)