Memungkinkan AI agent untuk melakukan kontrol debugger penuh terhadap aplikasi Java yang sedang berjalan melalui JDWP/JDI, termasuk memeriksa state dan mengubah nilai.
Juga dikenal sebagai FgForrest/mcp-jdwp-java
- Stars
- 34
- Tools
- 50
- Unduhan, 30 hari
- —
- Versi terbaru
- —
- Commit terakhir
- 14 Nov 2026
- Lisensi
- MIT
Apa itu jdwp-java MCP?
jdwp-java adalah MCP server. Memungkinkan AI agent untuk melakukan kontrol debugger penuh terhadap aplikasi Java yang sedang berjalan melalui JDWP/JDI, termasuk memeriksa state dan mengubah nilai. Server ini menyediakan 50 tool, seperti jdwp_connect, jdwp_disconnect, jdwp_reconnect.
Ringkasan dibuat dari README proyek
Tentang jdwp-java MCP server
MCP server ini memungkinkan Anda melakukan debugging aplikasi Java yang sedang berjalan langsung dari dalam AI agent. Server ini menyediakan interface bagi agent untuk melakukan inspeksi mendalam dan manipulasi runtime pada JVM menggunakan protokol JDWP dan JDI. Alih-alih hanya membaca stack trace, agent dapat menjelajahi seluruh object graph, membaca variabel lokal, serta memeriksa field atau thread. Hal ini memberikan kedalaman diagnostik yang setara dengan IDE profesional seperti IntelliJ atau Eclipse.
Server ini beroperasi secara lokal melalui stdio dan terhubung ke target JVM melalui socket JDWP lokal. Anda memerlukan JDK 17 atau versi lebih tinggi untuk menjalankannya, meskipun JDK 21 diperlukan untuk melakukan build server dari source. Pengguna dapat mengintegrasikannya dengan Claude Code menggunakan plugin atau registrasi manual. Setelah terhubung, agent dapat menggunakan lebih dari 50 tools untuk memasang conditional breakpoint, mengevaluasi ekspresi Java arbitrer, bahkan mengubah nilai field atau variabel lokal untuk menguji berbagai skenario tanpa harus merestart aplikasi.
Apa yang bisa Anda lakukan dengannya
- 1Inspeksi runtime object stateAgent dapat memeriksa seluruh object graph dan membaca variabel lokal untuk memahami mengapa suatu nilai tertentu tidak sesuai.
- 2Uji logika melalui runtime mutationDeveloper dapat meminta AI untuk mengubah nilai field atau variabel lokal saat program sedang dijeda untuk melihat bagaimana reaksi aplikasi tersebut.
- 3Pantau perubahan field dengan watchpointsDeveloper dapat melacak secara tepat kapan dan di mana sebuah field tertentu dimodifikasi oleh aplikasi yang sedang berjalan.
Ditulis berdasarkan README proyek. Setiap use case diperiksa terhadap kutipan dari README tersebut.
Tool jdwp-java MCP 50
Terdeteksi dari READMEjdwp_connectjdwp_disconnectjdwp_reconnectjdwp_wait_for_attachjdwp_get_versionjdwp_get_threadsjdwp_dump_locksjdwp_get_stackjdwp_get_localsjdwp_get_fieldsjdwp_to_stringjdwp_get_breakpoint_context
| jdwp_connect | — | |
| jdwp_disconnect | — | |
| jdwp_reconnect | — | |
| jdwp_wait_for_attach | — | |
| jdwp_get_version | — | |
| jdwp_get_threads | — | |
| jdwp_dump_locks | — | |
| jdwp_get_stack | — | |
| jdwp_get_locals | — | |
| jdwp_get_fields | — | |
| jdwp_to_string | — | |
| jdwp_get_breakpoint_context | — |
Tampilkan semua 50 toolTampilkan lebih sedikit tool
jdwp_get_current_threadjdwp_resumejdwp_resume_threadjdwp_suspend_threadjdwp_resume_until_eventjdwp_step_overjdwp_step_intojdwp_step_outjdwp_set_breakpointjdwp_set_logpointjdwp_set_fast_logpointjdwp_set_fast_breakpointjdwp_restore_fast_logpointsjdwp_clear_breakpointjdwp_set_exception_breakpointjdwp_set_exception_logpointjdwp_set_field_breakpointjdwp_set_field_logpointjdwp_set_breakpoint_dependencyjdwp_clear_breakpoint_dependencyjdwp_disarm_until_triggerjdwp_evaluate_expressionjdwp_assert_expressionjdwp_set_localjdwp_set_fieldjdwp_get_eventsjdwp_clear_eventsjdwp_diagnosejdwp_attach_watcherjdwp_detach_watcherjdwp_list_watchers_for_breakpointjdwp_evaluate_watchersjdwp_mark_instancejdwp_unmark_instancejdwp_rename_markjdwp_overviewjdwp_clearjdwp_reset
| jdwp_get_current_thread | — | |
| jdwp_resume | — | |
| jdwp_resume_thread | — | |
| jdwp_suspend_thread | — | |
| jdwp_resume_until_event | — | |
| jdwp_step_over | — | |
| jdwp_step_into | — | |
| jdwp_step_out | — | |
| jdwp_set_breakpoint | — | |
| jdwp_set_logpoint | — | |
| jdwp_set_fast_logpoint | — | |
| jdwp_set_fast_breakpoint | — | |
| jdwp_restore_fast_logpoints | — | |
| jdwp_clear_breakpoint | — | |
| jdwp_set_exception_breakpoint | — | |
| jdwp_set_exception_logpoint | — | |
| jdwp_set_field_breakpoint | — | |
| jdwp_set_field_logpoint | — | |
| jdwp_set_breakpoint_dependency | — | |
| jdwp_clear_breakpoint_dependency | — | |
| jdwp_disarm_until_trigger | — | |
| jdwp_evaluate_expression | — | |
| jdwp_assert_expression | — | |
| jdwp_set_local | — | |
| jdwp_set_field | — | |
| jdwp_get_events | — | |
| jdwp_clear_events | — | |
| jdwp_diagnose | — | |
| jdwp_attach_watcher | — | |
| jdwp_detach_watcher | — | |
| jdwp_list_watchers_for_breakpoint | — | |
| jdwp_evaluate_watchers | — | |
| jdwp_mark_instance | — | |
| jdwp_unmark_instance | — | |
| jdwp_rename_mark | — | |
| jdwp_overview | — | |
| jdwp_clear | — | |
| jdwp_reset | — |
Konfigurasi
MCP_TIMEOUTMCP_TOOL_TIMEOUT
| Nama | Deskripsi | Tipe |
|---|---|---|
| MCP_TIMEOUT | — | |
| MCP_TOOL_TIMEOUT | — |
Pengecekan otomatis
Berjalan 13 Nov 2026Laporan lengkap →Lolos checks otomatis kami. Ini bukan audit keamanan: baca kodenya dan berikan server akses paling terbatas yang dibutuhkannya. Cara kerja checks
- Source repository terhubung dan dapat dijangkauLolos
- Lisensi open source (OSI)Lolos
- Ada commit atau release dalam satu tahun terakhirLolos
- Nama package tidak mirip dengan package populerTidak diperiksa
- Remote endpoint menggunakan HTTPSTidak diperiksa
- Tools yang bersifat destruktif telah diberi labelLolos
- Tidak ada instruksi tersembunyi untuk modelLolos
- Tidak ada advisory kritis pada paket atau dependensinyaTidak diperiksa
README
Lihat di GitHubMCP JDWP Inspector
MCP server that gives AI agents full debugger control over running Java applications — inspect state, set breakpoints, evaluate expressions, and mutate values at runtime via JDWP/JDI.
Release notes — see
CHANGELOG.md. 2.0.0 is a breaking release: severaljdwp_clear_*tools were unified, and field watchpoints are new.
Built on the foundations of mcp-jdwp-java by Nicolas Vautrin — the original project that provided core JDI connectivity, thread/stack/variable inspection, stepping, and basic breakpoint management. Everything described below as "beyond standard JDWP" was built on top of that base.
What you get (TL;DR)
An MCP server that speaks JDWP — the same protocol IntelliJ / Eclipse / VS Code use to attach — and exposes the debugger to Claude Code as 50 tools + 2 MCP resources over STDIO. Once the agent attaches to your JVM, it can:
- See runtime state, not stack traces — read locals, fields, threads, the entire object graph
- Evaluate arbitrary Java in the suspended frame — compiled to real bytecode, full classpath, including private/package-private members
- Mutate the running program — set locals, write fields, test "what if?" without restart
- Stop on the bad case only — conditional breakpoints, exception breakpoints at the throw site, deferred BPs that arm before the class loads, chains that gate one BP on another
- Trace without stopping — line/exception/field logpoints write to an event log, the thread runs on
- Catch the writer of a mutating field — watchpoints suspend at every bytecode store to the field, wherever it is; a write through reflection or
Unsafeis the one they cannot see, and the[VM_DEATH]reply's hit count shows when a watchpoint stayed silent - Pure STDIO — no daemon, no extra port, no GUI
The agent gets the same power as IntelliJ's debugger, with extras that exist specifically because an agent isn't a human staring at a UI (filtering, blocking resume, one-shot context dumps, recursion guards, reconnect after a wedge).
Security & trust
This MCP server runs entirely on your local machine:
- No network calls — the server communicates with Claude Code over STDIO and with the target JVM over a local JDWP socket. It makes zero outbound HTTP/internet requests. No telemetry, no analytics, no phone-home.
- Built from source — the JAR is compiled locally on your machine from the source code in this repository (either via the plugin auto-build hook or manually with
./mvnw clean package). No pre-built binaries are downloaded or distributed. - Auditable — the full source is here. The server is a standard Spring Boot application with no obfuscation or native code. Try asking Claude Code itself: "audit these sources for anything that touches network or filesystem beyond the JVM target".
Quick start
Prerequisites
- JDK 17+ on PATH to run the server (must be a JDK, not a JRE — JDI lives in
jdk.jdi). - JDK 21+ to build from source — the build toolchain pins to JDK ≥ 21 because Error Prone 2.48 ships Java-21 bytecode. The bytecode target stays at Java 17, so the resulting JAR still runs on a JDK 17 runtime.
No separate Maven install is required — the repository ships with the Maven Wrapper (./mvnw), which downloads a pinned Maven 3.9.x into ~/.m2/wrapper/ on first use. Every build command in this README uses the wrapper; the SessionStart hook does too, and falls back to mvn on PATH when the wrapper cannot set itself up.
1. Install the plugin
Option A: Plugin marketplace (recommended)
Installs the MCP server, the java-debug skill (debugging workflows, recipes, gotchas), and the MCP server registration in one step:
/plugin marketplace add https://github.com/FgForrest/mcp-jdwp-java.git
/plugin install jdwp-debugging@mcp-jdwp-java
The server JAR is built automatically on first session start via the bundled ./mvnw wrapper (requires JDK 21+ — no Maven install needed). The hook is content-aware: it rebuilds when the JAR is missing or, in a git checkout, when HEAD moves, so git pull + restart picks up updates without manual intervention. When the wrapper cannot set itself up (for example, ~/.m2 is read-only in a sandbox), the hook tries mvn -o and then mvn from PATH. If every attempt fails, a warning at session start names the build log and the exact command to build by hand. A build runs while Claude Code starts the MCP server and the JAR is absent until it finishes, so after a build (first install, update, moved HEAD) jdwp-inspector may show as failed in /mcp; reconnect it there once the build is done. Restart Claude Code to pick up the plugin.
If you prefer to register the MCP server directly without the plugin (no skill, no auto-build):
1. Build the JAR:
git clone https://github.com/FgForrest/mcp-jdwp-java.git
cd mcp-jdwp-java
./mvnw clean package -DskipTests # use mvnw.cmd on Windows
2. Register with Claude Code:
claude mcp add jdwp-inspector -s user \
-e MCP_TIMEOUT=30000 \
-e MCP_TOOL_TIMEOUT=120000 \
-- java --add-modules jdk.jdi,jdk.attach -jar /path/to/mcp-jdwp-java.jar
To change the JDWP port (default 5005), add -DJVM_JDWP_PORT=12345 before -jar.
The MCP_TIMEOUT and MCP_TOOL_TIMEOUT environment variables are important — JVM startup is not instant (class loading, Spring context initialization), so the default MCP timeouts will cause Claude Code to give up before the server is ready. MCP_TIMEOUT=30000 gives the server 30 seconds to start, and MCP_TOOL_TIMEOUT=120000 allows up to 2 minutes for long-running tools like first-time expression evaluation (which discovers the target's classpath and compiles bytecode).
Re-installing requires removing first: claude mcp remove jdwp-inspector -s user
Drop -s user to scope to the current project only.
.mcp.json:
{
"mcpServers": {
"jdwp-inspector": {
"command": "java",
"args": [
"--add-modules", "jdk.jdi,jdk.attach",
"-jar", "/path/to/mcp-jdwp-java.jar"
],
"env": {
"MCP_TIMEOUT": "30000",
"MCP_TOOL_TIMEOUT": "120000"
}
}
}
}
The plugin-managed install also passes -DLOG_PATH=${CLAUDE_PLUGIN_ROOT}/logs/mcp-jdwp-inspector.log so the server's SLF4J log lands inside the plugin directory; add the same flag if you want a fixed log path here.
2. Launch your Java application with JDWP
Maven Surefire (test debugging):
mvn test -Dmaven.surefire.debug
Starts the JVM with JDWP on port 5005, suspended until a debugger connects.
Any Java application:
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005
3. Attach from Claude Code
From there, just tell the agent what to do — "attach to JDWP, set a breakpoint in OrderService.createOrder line 42, run the failing test, dump the context when it hits". The bundled java-debug skill teaches the agent the right tool sequence; the next section gives you a smoke test that exercises the surface.
Find the Bug — test flights
The jdwp-sandbox module ships 10 deliberately broken Java classes. Each one compiles fine, looks reasonable at first glance, and fails its test with a confusing message. Your job: attach with the JDWP MCP server and find the root cause.
This doubles as a setup verification — if you can solve these, everything works.
Each flight is built so that one tool group is the path of least resistance, and lists a par — the minimum tool calls that cleanly reveal the root cause. Hitting par is the elegant solve; the suite as a whole exercises the full tool surface (expression eval, exception breakpoints, field watchpoints, event history, marks, logpoints, runtime mutation, multi-thread inspection, and in-target probes).
Just installed the plugin? Grab the sandbox zip
The fastest path for fresh plugin users is a self-contained zip — no clone, no reactor build:
curl -L -o jdwp-sandbox.zip \
https://github.com/FgForrest/mcp-jdwp-java/releases/latest/download/jdwp-sandbox.zip
unzip jdwp-sandbox.zip && cd jdwp-sandbox
Inside, you get a parentless Maven project with src/, a pom.xml, a flight-game README.md, and a CLAUDE.md that briefs the agent on the game's house rules. Open the folder in Claude Code and ask it to play flight #1 — the bundled CLAUDE.md keeps it honest (no peeking at source, no spoiler-fetching). Continue with the workflow below.
How to launch a test flight
Start Claude Code from the sandbox folder (or this repo's root, if you cloned) and type:
Use JDWP to debug <TestClass> in the jdwp-sandbox module — the test is failing, find the root cause.
#1 The Phantom Session
Difficulty: Moderate | Test: SessionStoreTest | Package: session | Par: 4 | Exercises: expression eval
Symptom: retrieve() returned null — a session was stored, upgraded, and then... vanished from the map.
UserSession is used as a HashMap key, with both userId and role in hashCode(). upgradeUserRole() calls session.upgradeRole("PREMIUM"), which mutates role — changing the hashCode while the key is still in the map. The entry sits in the old hash bucket; lookups compute the new hash and search the wrong bucket.
Debug path: Breakpoint before and after upgradeRole(). Use jdwp_assert_expression("session.hashCode()", "<value-before>") after the upgrade — MISMATCH confirms the hash drifted.
#2 The Swallowed Exception
Difficulty: Hard | Test: EventBusTest | Package: events | Par: 4 | Exercises: exception breakpoint + trigger gate
Symptom: expected stock < 100 but was 100 and an empty error summary — the order was supposed to reserve inventory, but nothing happened and nobody complained.
OrderEvent narrows the raw quantity through byte (200 → -56), so Inventory.reserve() throws IllegalStateException. EventBus.dispatch() runs each handler on a single-thread executor as a fire-and-forget task — the Future is never inspected, so the exception is captured inside java.util.concurrent.FutureTask.run (a JDK frame) and discarded. No sandbox frame ever holds the throwable, so a breakpoint-context dump has nothing to show, and getErrorSummary() stays empty.
Debug path: Because the exception is caught (by FutureTask), an uncaught-only breakpoint never fires. Set a line BP at the test/dispatch entry as a trigger, then jdwp_set_exception_breakpoint("java.lang.IllegalStateException", caught=true, triggerBreakpointId=<trigger>) so bootstrap exceptions don't drown the signal. The throw site suspends with qty = -56 in the frame's locals.
#3 The Time Traveler's Config
Difficulty: Hard | Test: ConfigurationProviderTest | Package: config | Par: 4 | Exercises: field watchpoint + event history
Symptom: expected timeout=5000 but was 0 — the timeout was set during construction, yet it reads back as the default.
ConfigurationProvider's constructor calls init(5000), so the timeout is correct. Then runMaintenanceSweep() starts a background config-reaper thread that wrongly treats the live instance as stale and calls resetToDefaults(), writing the timeout back to 0. By the time the test reads it, the heap shows 0 and no local holds 5000 — a single context dump looks like the value was never set.
Debug path: jdwp_set_field_breakpoint(className="…config.Configuration", fieldName="timeout", mode="modification"), then jdwp_get_events shows the two stores in order — 0→5000 on the main thread, then 5000→0 on config-reaper. jdwp_get_stack on the second event names the reaper as the clobberer.
#4 The Audit That Lies
Difficulty: Hard | Test: TransferServiceTest | Package: bank | Par: 4 | Exercises: field watchpoint + stack narration
Symptom: expected discrepancy=0 but was non-zero — money is neither created nor destroyed, yet the audit says the books don't balance.
TransferService.transfer() is not atomic: it calls source.withdraw(), then auditService.snapshotBalances(), then destination.deposit(). The snapshot captures the intermediate state where money has left the source but hasn't arrived at the destination — showing a total of 1500 instead of 2000.
Debug path: jdwp_set_field_breakpoint on AuditService.lastTotalSnapshot (modification). It fires mid-transfer with the stack at TransferService.transfer between the debit and the credit; jdwp_get_stack plus a live balance read confirm the captured dip.
#5 The Field That Lies
Difficulty: Hard | Test: UserProfileTest | Package: userprofile | Par: 3 | Exercises: field watchpoint blind spot + before/after bisect
Symptom: expected: <Alice> but was: <alice> — the welcome message rendered correctly, yet the user's stored display name has silently changed casing.
LoginNormalizer.welcomeMessage calls a private canonicalForm helper, which delegates to a static DisplayNameMirror nested class. The mirror uses Field.setAccessible(true) + Field.set(profile, canonical) to write the lower-cased form straight into UserProfile.displayName, bypassing the public setter. From the test's side the formatter looks read-only, and a field watchpoint cannot see the write either: JDI watchpoints fire only on the putfield/getfield bytecodes (and JNI field access). Field.set ends in an Unsafe store straight to memory, which trips no watchpoint, in modification mode or in access mode.
Debug path: The tell is a modification watchpoint that fires once, for the constructor's ordinary assignment, and never again, although the value flips before the assertion: the write does not go through a field store. The par path takes three calls:
jdwp_set_field_breakpoint(className="one.edee.jdwp.sandbox.userprofile.UserProfile", fieldName="displayName", mode="modification"). The class is not loaded yet, so the watchpoint waits for it.jdwp_resume_until_event: the watchpoint fires in theUserProfileconstructor, which stores"Alice".jdwp_resume_until_event: the test fails and its JVM exits. The[VM_DEATH]reply lists the watchpoint with one hit. The only store the JVM saw wrote"Alice", yet the test read"alice", so the write is reflective or goes throughUnsafe. Between the two, the test calls onlywelcomeMessage, on line 24.
Bisecting takes three calls as well and shows where the value flips, though not how: jdwp_set_breakpoint("one.edee.jdwp.sandbox.userprofile.UserProfileTest", 27), just after welcomeMessage, then jdwp_resume_until_event, then jdwp_evaluate_expression("profile.getDisplayName()"), which is already "alice". When the old and new strings could be equal, compare System.identityHashCode(profile.getDisplayName()) instead.
That is the evidence to read LoginNormalizer with: canonicalForm → DisplayNameMirror.mirror → Field.set. To name the writing call from the debugger alone, break at line 24 instead and keep bisecting inside welcomeMessage with jdwp_step_into / jdwp_step_over, comparing the identity after each line: it flips across DisplayNameMirror.mirror(profile, canonical) in canonicalForm. A line BP on UserProfile.setDisplayName never fires — the setter is genuinely unused.
#6 The Doppelgänger Cart
Difficulty: Moderate | Test: CheckoutTest | Package: cart | Par: 6 | Exercises: marked instances ($label)
Symptom: expected 45.0 but was 0.0 — the cart goes through pricing and discounting, yet its total never changes.
Checkout.process runs the cart through validate → price → discount. validate was changed to return a defensive snapshot — return new Cart(cart) — so every later stage mutates the copy. process reassigns its local to the copy as it threads the stages, so no single frame holds both the caller's cart and the copy at once. The test ignores the return value (assuming in-place mutation), so its cart stays at total 0. The copy's fields are identical to the original — only identity differs.
Debug path: Break in the test once the cart exists, jdwp_mark_instance(label="input", objectId=<id>) to pin it, then set a breakpoint in price (or applyDiscount) with condition cart != $input. It fires — the stage is working on a doppelgänger, not the caller's cart.
#7 The Heisenbug Race
Difficulty: Hard | Test: RaceCounterTest | Package: race | Par: 3 | Exercises: logpoints (non-stopping)
Symptom: expected 2 but was 1 — two threads each increment a counter once, but one increment vanishes.
RaceCounter.increment reads the count, waits at a CyclicBarrier so both threads have read before either writes, then writes back read + 1. Both threads read 0 and both write 1 — one increment is lost. A suspending breakpoint serializes the threads and makes you juggle two parked threads to reconstruct what happened.
Debug path: Set a non-suspending jdwp_set_logpoint at the read site logging the thread name and count (or a field logpoint on count). Let the test run, then jdwp_get_events — both racer-1 and racer-2 are recorded reading 0 before either writes, which is the lost update laid out in order.
#8 The Magic Patch
Difficulty: Warm-up | Test: DateParserTest | Package: parser | Par: 4 | Exercises: runtime mutation (set_local)
Symptom: NumberFormatException: For input string: "15 " — a date string that looks right fails to parse.
DateParser.parse splits YYYY-MM-DD and Integer.parseInts each part. The feed delivers "2026-05-15 " with a trailing space, so parseInt("15 ") throws. The parser never trims its input.
Debug path: Four calls. jdwp_set_breakpoint at the top of parse, jdwp_resume_until_event to stop there, jdwp_set_local("input", "2026-05-15") to drop the trailing space (or break on the test's line 19 and patch fromFeed instead), and jdwp_resume. The parse now succeeds and the test passes — confirming the trailing space is the whole story, with no rebuild. The exception message already shows the stray space, so the stop needs no inspection call; the confirming resume counts.
#9 The Polite Standoff
Difficulty: Hard | Test: TransferDeadlockTest | Package: deadlock | Par: 4 | Exercises: multi-thread inspection (no breakpoints)
Symptom: The test hangs and then fails its join — both transfers never complete.
Account.transfer locks the source account, pauses, then locks the destination. Two transfers in opposite directions (A→B and B→A) each grab their first lock and then wait forever for the other's — a classic lock-ordering deadlock.
Debug path: No breakpoint can fire — the threads execute nothing. jdwp_get_threads shows transfer-A-to-B and transfer-B-to-A in MONITOR state; jdwp_dump_locks prints the lock graph with both monitor holders and the inverse-order cycle; jdwp_get_stack on each thread shows the lock-ordering inversion. (Trying jdwp_evaluate_expression / jdwp_to_string on these threads is refused — invoking a method on a monitor-blocked thread would hang the debugger, which is itself the tell.)
#10 The Oversold House
Difficulty: Hard | Test: BoxOfficeTest | Package: boxoffice | Par: 4 | Exercises: in-target probes (fast logpoint / fast breakpoint, with the user's consent)
Symptom: [Tickets sold for 300 performances of 200000 seats] expected: 60000000L but was: 60001634L — the season ends oversold by a few tickets per performance.
Solved when: you name one specific double sale: the seat, the window that sold it first and the window that sold it again. Knowing that the house is oversold, or by how much, is not enough.
BoxOffice.trySell checks that the seat is free, prices it (reading the pricing desk's volatile surcharge), and only then records the buyer: a check-then-act race. Two windows that check the same free seat within the pricing step both sell it, and sold counts both. About six seats per performance are sold twice. Customer numbers are window numbers, and owners[] keeps only the last buyer, so counting seats against sold() after the fact shows the overselling but cannot name the window that sold a seat first. Only catching the collision can.
Debug path: On the sell line (BoxOffice.java:54, seatOwners[seat] = customer), the condition seatOwners[seat] != 0 is true when another window sold the seat after this one checked it. Both in-target tools rewrite the class's bytecode, so each needs the user's explicit consent: the first call, without userApproved, arms nothing and returns a risk briefing to relay word for word. BoxOffice is not loaded when the test JVM starts; the approved probe waits for the class to load and arms on its next hit.
- Fast logpoint, 4 calls (par):
jdwp_set_fast_logpointonone.edee.jdwp.sandbox.boxoffice.BoxOfficeline 54, with that condition and the expressionseat + " sold to " + seatOwners[seat] + ", resold to " + customer, withoutuserApproved: the risk briefing, relayed to the user.- The same call with
userApproved=true, once the user agrees. jdwp_resume_until_event: the test runs to its end. The reply is[VM_DEATH]and says how manyFAST_LOGPOINThits were recorded.jdwp_get_events: the double sales, each with its seat and the first and second window.
- Fast breakpoint, 5 calls: the briefing and the approved
jdwp_set_fast_breakpointcall with the same condition, thenjdwp_resume_until_event: the first double sale stops with every window parked.jdwp_get_breakpoint_contextshowsseatand its second buyer,customer.jdwp_evaluate_expression("seatOwners[seat]")gives the first buyer; array elements are not in the context dump.
The logpoint does not catch every double sale. Its condition reads a plain int[] without synchronization, so a window that reads the seat before the other window's write is visible logs nothing; one live run caught 1,460 of 2,472 double sales. One is enough to solve the flight. The probe also sits inside the race window, between the check and the write, so it widens the race rather than hiding it: runs with the probe sold more seats twice, not fewer.
An ordinary conditional breakpoint or logpoint on that line goes through the debugger on each of about 60 million passes, at 3–30 ms each. The box office closes first, and the serialized windows stop colliding.
Pertanyaan tentang jdwp-java MCP
Tool apa yang disediakan jdwp-java MCP?
Server ini menyediakan 50 tool, termasuk jdwp_connect, jdwp_disconnect, jdwp_reconnect, jdwp_wait_for_attach, jdwp_get_version.
Apa yang bisa dilakukan AI agent dengan server ini?
Agent dapat memeriksa runtime state seperti variabel lokal dan field, mengevaluasi kode Java arbitrer dalam frame yang sedang suspended, mengubah program yang sedang berjalan, serta memasang conditional breakpoint atau exception breakpoint.
Apa persyaratan sistem untuk menjalankan server ini?
Anda perlu menginstal JDK 17+ pada PATH Anda untuk menjalankan server, dan JDK 21+ diperlukan jika ingin melakukan build dari source.
Bagaimana cara menghubungkannya ke aplikasi Java saya?
Anda harus menjalankan aplikasi Java Anda dengan JDWP yang sudah diaktifkan, misalnya menggunakan argumen -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005.