> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nelieo.com/llms.txt
> Use this file to discover all available pages before exploring further.

# JVM / Java Probe: JVMTI State Extraction for Java Apps

> How NSP attaches to running Java processes via JVMTI, reads field values and invokes methods using JNI reflection, without an agent JAR or source code.

The JVM probe reads live state from any running Java Virtual Machine process by loading a native JVMTI agent through the standard JVM Attach API — the same mechanism used by production profilers such as YourKit and JProfiler. Once attached, it uses JNI reflection to enumerate loaded classes, read field values, and invoke methods on live Java objects. You do not need to modify the application, add an agent JAR to the JVM startup arguments, or have access to any source code.

***

## How JVMTI Attachment Works

The JVM Attach API (`com.sun.tools.attach.VirtualMachine`) is part of the standard JDK tooling. NSP's daemon calls it at runtime to load the JVMTI agent into the target JVM without pausing or restarting the process:

```text theme={null}
axon-daemon
  │
  └─ JVM probe attach(pid)
        └─ VirtualMachine.attach(pid) → loads axon_jvmti_agent.jar
              │
              ├─ JVMTI GetLoadedClasses   ← enumerate all loaded types
              ├─ JNI  GetFieldID           ← locate fields by name
              ├─ JNI  GetObjectField       ← read field values
              └─ JNI  CallObjectMethod     ← invoke methods with arguments
```

All field reads and method invocations execute on a JVM thread inside the target's own thread pool. The target JVM is **never paused** — unlike heap dump approaches that require a stop-the-world phase.

***

## Schema Learning

On first attachment, the probe performs a cold probe that walks all loaded classes, identifies state-bearing fields by type and naming convention, and builds a **schema** that is persisted in the local SQLite registry. Schema learning includes:

* Mapping Java class names and field names to SSF dot-notation keys
* Detecting collections (lists, maps, arrays) and inferring element types
* Scoring fields by semantic relevance (discarding JVM internals and synthetic fields)
* Queuing unrecognized objects for LLM-assisted label enrichment

After the schema is cached, subsequent polls skip the class enumeration step and go straight to targeted field reads, reducing per-cycle latency to **20–100ms**.

<Info>
  Cold probe time for JVM applications is typically **10–60 seconds**, depending on the number of loaded classes and the complexity of the application's data model. You will see `probe_status: "cold_probe"` in `AppSummary` during this phase.
</Info>

***

## Supported Applications

The following applications have validated entries in the app signature database. Any Spring Boot or standard Java desktop application is also supported via field and method introspection, even without a pre-built signature.

| Application          | Package Prefix      | Notable State                             |
| -------------------- | ------------------- | ----------------------------------------- |
| SAP ERP              | `com.sap`           | Business object fields, transaction state |
| Eclipse IDE          | `org.eclipse`       | Project tree, open editors, build state   |
| IntelliJ IDEA        | `com.intellij`      | Project/file state, run configurations    |
| Jenkins              | `hudson`, `jenkins` | Build pipeline status, job queue          |
| Elasticsearch        | `org.elasticsearch` | Index stats, cluster health, shard state  |
| ArduPilot GCS (Java) | `com.ardupilot`     | MAVLink telemetry, flight mode, waypoints |

***

## Confidence Baseline

The JVM probe achieves consistently high confidence because the JVM retains full reflection metadata at runtime — class names, field names, and type signatures are always available, even in production builds.

| Scenario                      | Confidence Range |
| ----------------------------- | ---------------- |
| Cold probe (first attachment) | 0.93–0.97        |
| After schema cache populated  | 0.95–0.98        |
| Obfuscated JAR (ProGuard/R8)  | 0.70–0.85        |
| Custom classloader / OSGi     | 0.80–0.93        |

These confidence values unlock the following action tiers:

| Confidence                | Action Gating                               |
| ------------------------- | ------------------------------------------- |
| 0.95–0.98 (cached schema) | All actions, including `irreversible_write` |
| 0.93–0.97 (cold probe)    | `reversible_write` and `read` actions       |
| 0.70–0.85 (obfuscated)    | `read` actions only                         |

***

## Reading State via the Python SDK

Access the live Java object graph through the standard `state` API or through the JVM-specific session proxy:

<Tabs>
  <Tab title="Standard State API">
    ```python theme={null}
    import asyncio
    from nelieo_nsp import NSPClient

    async def main():
        async with NSPClient() as client:
            session = await client.session("ardupilot-gcs-4521")
            state = await session.state()

            # Dot-notation keys — same as any other runtime
            altitude = state.get_float("vehicle.altitude")
            mode = state.get_str("vehicle.flight_mode")
            armed = state.get_bool("vehicle.is_armed")

            print(f"Mode: {mode} | Alt: {altitude:.1f}m | Armed: {armed}")

    asyncio.run(main())
    ```
  </Tab>

  <Tab title="JVM Session Proxy">
    ```python theme={null}
    async def main():
        async with NSPClient() as client:
            session = await client.session("ardupilot-gcs-4521")

            # Direct field read — bypasses SSF key lookup
            alt = await session.jvm.read_field("com.ardupilot.GCS", "altitude")
            mode = await session.jvm.read_field("com.ardupilot.GCS", "flightMode")
            print(f"Altitude: {alt:.1f}m  Mode: {mode}")
    ```
  </Tab>
</Tabs>

***

## Invoking Java Methods

The JVM probe can invoke methods on live Java objects with full type coercion. Python types are automatically mapped to the correct JVM types at the call site:

```python theme={null}
# Parameterless method call
status = await session.jvm.invoke("com.ardupilot.GCS", "getStatusText")
print(status)  # "OK: 3D GPS Fix"

# Method with typed arguments
await session.jvm.invoke(
    "com.ardupilot.WaypointManager",
    "addWaypoint",
    [34.052, -118.243, 150.0],  # lat (double), lon (double), alt (double)
)

# String argument
await session.jvm.invoke(
    "com.ardupilot.GCS",
    "setMode",
    ["GUIDED"],
)
```

### Type Coercion Reference

| Python Type | JVM Type                       |
| ----------- | ------------------------------ |
| `bool`      | `boolean` / `Boolean`          |
| `int`       | `int`, `long`, `short`, `byte` |
| `float`     | `double`, `float`              |
| `str`       | `String`                       |
| `list`      | `List<T>`, `T[]`               |
| `dict`      | `Map<String, Object>`          |
| `None`      | `null`                         |

***

## No Agent JAR or Source Code Required

NSP's JVMTI integration is fully self-contained. The daemon loads `axon_jvmti_agent.jar` into the target process at runtime via the Attach API. You do not need to:

* Add any JAR to the application's classpath or startup arguments
* Modify the application's launch script or container entrypoint
* Provide source code, bytecode, or build artifacts
* Restart the JVM

<Note>
  The JVMTI Attach API requires that the target JVM was launched by the same OS user as `axon-daemon`, or that `axon-daemon` is running with elevated privileges. On Linux, this typically means running the daemon as the same user that launched the Java application.
</Note>

***

## Error Reference

| Error Code             | Description                                   | Recovery                                     |
| ---------------------- | --------------------------------------------- | -------------------------------------------- |
| `jvm_not_attached`     | JVMTI agent not loaded yet                    | Wait for `probe_status: "active"`            |
| `jvm_class_not_found`  | Class not loaded in target JVM                | Verify the fully-qualified class name        |
| `jvm_method_not_found` | Method name or signature mismatch             | Check method name and argument types         |
| `jvm_invoke_timeout`   | Method did not return within `timeout_ms`     | Increase `timeout_ms` or check for deadlocks |
| `jvm_type_error`       | Python argument cannot be coerced to JVM type | Check the type coercion table above          |

<AccordionGroup>
  <Accordion title="Does the JVM probe work with Spring Boot services?">
    Yes. Any Spring Boot application running in a standard JVM is supported. The probe enumerates Spring bean types via JVMTI and reads their field values through JNI reflection. For web services, state includes request context objects and in-flight bean state.
  </Accordion>

  <Accordion title="What happens with obfuscated JARs?">
    If the application was compiled with ProGuard or R8, class and field names may be minified (e.g. `a.b.c`). The cold probe queues these for LLM-assisted label enrichment, which maps the obfuscated names to semantic labels based on field types and values. Confidence will be lower (0.70–0.85) until enrichment completes.
  </Accordion>

  <Accordion title="Does the JVM probe support multi-JVM environments?">
    Yes. Each JVM process receives its own `app_id` and independent probe lifecycle. If multiple JVMs are running on the same host (e.g. a Jenkins controller and agent), each is tracked and probed independently.
  </Accordion>
</AccordionGroup>
