JDTLS uses ephemeral temp directory for -data and lacks JVM heap configuration, causing repeated full index rebuilds and potential OOM #9038

Open
opened 2026-02-16 18:11:29 -05:00 by yindo · 0 comments
Owner

Originally created by @tongsh6 on GitHub (Feb 10, 2026).

Originally assigned to: @rekram1-node on GitHub.

Summary

In addition to the multi-instance spawning issue reported in #11368, the built-in JDTLS configuration has two more problems that severely degrade the experience on large Java projects:

  1. mkdtemp for -data directory: Every JDTLS launch creates a unique temp directory via fs.mkdtemp(path.join(os.tmpdir(), "opencode-jdtls-data")). This means all workspace indexes, project metadata, and dependency caches are lost when the process exits. On a large multi-module Maven project (466 pom.xml files, 97 independent module roots), a full index rebuild takes 5-10 minutes — and this happens every single time.

  2. No -Xms/-Xmx JVM flags: The built-in spawn command passes no heap size arguments. The JVM defaults to ~1/4 of physical memory, which may be insufficient for indexing thousands of class files, type hierarchies, and symbol tables. This leads to frequent Full GC pauses and potential OOM crashes during index construction.

Root Cause

From the built-in JDTLS spawn logic (binary reverse-engineered via strings + grep on opencode v1.1.53):

async spawn(root) {
  const dataDir = await fs.mkdtemp(path.join(os.tmpdir(), "opencode-jdtls-data"));
  return {
    process: spawn(java, [
      "-jar", launcherJar,
      "-configuration", configFile,
      "-data", dataDir,  // ← ephemeral, lost on exit
      // No -Xms, -Xmx, no GC tuning
      "--add-modules=ALL-SYSTEM",
      "--add-opens java.base/java.util=ALL-UNNAMED",
      "--add-opens java.base/java.lang=ALL-UNNAMED"
    ], { cwd: root })
  };
}

Problem 1: Ephemeral data directory

  • fs.mkdtemp creates /tmp/opencode-jdtls-data-XXXXXX — a new unique dir each time
  • When JDTLS exits, all indexed data is effectively orphaned (or cleaned by the OS)
  • Next launch starts a brand new index from scratch
  • On large projects this means 5-10 minutes of CPU-intensive indexing before LSP features become usable
  • Combined with #11368 (multiple instances), this creates N × full index builds

Problem 2: No JVM heap configuration

  • No -Xms or -Xmx flags → JVM uses its own defaults
  • For large Java monorepos, the default heap is often insufficient during index construction
  • Results in excessive GC pressure, long pauses, or outright OOM kills
  • No GC algorithm specified (G1GC is recommended for large-heap, low-latency workloads)

Suggested Fix

For data directory persistence

Use a deterministic, persistent workspace directory derived from the project root path:

const crypto = require("crypto");
const projectHash = crypto.createHash("md5").update(root).digest("hex").slice(0, 8);
const dataDir = path.join(os.homedir(), ".cache", "opencode-jdtls", projectHash);
await fs.mkdir(dataDir, { recursive: true });

This ensures:

  • Same project always reuses the same index directory
  • Indexes survive across sessions
  • Different projects don't collide

For JVM heap configuration

Add reasonable defaults to the spawn arguments:

spawn(java, [
  "-Xms512m",
  "-Xmx4g",
  "-XX:+UseG1GC",
  "-XX:+UseStringDeduplication",
  "-jar", launcherJar,
  // ...rest
])

Consider making these configurable via opencode.json (e.g., lsp.jdtls.jvmArgs).

Current Workaround

Users can work around both issues by disabling the built-in JDTLS and defining a custom LSP server in project-level opencode.json:

{
  "lsp": {
    "jdtls": { "disabled": true },
    "java": {
      "command": ["/path/to/custom-jdtls-script"],
      "extensions": [".java"],
      "env": { "JDTLS_WORKSPACE": "/home/user/.cache/jdtls-workspace/my-project" }
    }
  }
}

The custom script sets -Xms512m -Xmx4g -XX:+UseG1GC and uses the JDTLS_WORKSPACE env var for a persistent -data directory.

Related

  • #11368 — JDTLS spawns redundant per-module processes (NearestRoot issue)
  • #12123 — PR fixing the multi-instance root detection

Environment

  • opencode v1.1.53
  • WSL2 (Ubuntu), Java 21 (Temurin)
  • Project: 466 pom.xml files, 97 independent Maven module roots
Originally created by @tongsh6 on GitHub (Feb 10, 2026). Originally assigned to: @rekram1-node on GitHub. ## Summary In addition to the multi-instance spawning issue reported in #11368, the built-in JDTLS configuration has two more problems that severely degrade the experience on large Java projects: 1. **`mkdtemp` for `-data` directory**: Every JDTLS launch creates a unique temp directory via `fs.mkdtemp(path.join(os.tmpdir(), "opencode-jdtls-data"))`. This means all workspace indexes, project metadata, and dependency caches are lost when the process exits. On a large multi-module Maven project (466 pom.xml files, 97 independent module roots), a full index rebuild takes 5-10 minutes — and this happens every single time. 2. **No `-Xms`/`-Xmx` JVM flags**: The built-in spawn command passes no heap size arguments. The JVM defaults to ~1/4 of physical memory, which may be insufficient for indexing thousands of class files, type hierarchies, and symbol tables. This leads to frequent Full GC pauses and potential OOM crashes during index construction. ## Root Cause From the built-in JDTLS spawn logic (binary reverse-engineered via `strings` + `grep` on opencode v1.1.53): ```javascript async spawn(root) { const dataDir = await fs.mkdtemp(path.join(os.tmpdir(), "opencode-jdtls-data")); return { process: spawn(java, [ "-jar", launcherJar, "-configuration", configFile, "-data", dataDir, // ← ephemeral, lost on exit // No -Xms, -Xmx, no GC tuning "--add-modules=ALL-SYSTEM", "--add-opens java.base/java.util=ALL-UNNAMED", "--add-opens java.base/java.lang=ALL-UNNAMED" ], { cwd: root }) }; } ``` ### Problem 1: Ephemeral data directory - `fs.mkdtemp` creates `/tmp/opencode-jdtls-data-XXXXXX` — a new unique dir each time - When JDTLS exits, all indexed data is effectively orphaned (or cleaned by the OS) - Next launch starts a brand new index from scratch - On large projects this means 5-10 minutes of CPU-intensive indexing before LSP features become usable - Combined with #11368 (multiple instances), this creates N × full index builds ### Problem 2: No JVM heap configuration - No `-Xms` or `-Xmx` flags → JVM uses its own defaults - For large Java monorepos, the default heap is often insufficient during index construction - Results in excessive GC pressure, long pauses, or outright OOM kills - No GC algorithm specified (G1GC is recommended for large-heap, low-latency workloads) ## Suggested Fix ### For data directory persistence Use a deterministic, persistent workspace directory derived from the project root path: ```javascript const crypto = require("crypto"); const projectHash = crypto.createHash("md5").update(root).digest("hex").slice(0, 8); const dataDir = path.join(os.homedir(), ".cache", "opencode-jdtls", projectHash); await fs.mkdir(dataDir, { recursive: true }); ``` This ensures: - Same project always reuses the same index directory - Indexes survive across sessions - Different projects don't collide ### For JVM heap configuration Add reasonable defaults to the spawn arguments: ```javascript spawn(java, [ "-Xms512m", "-Xmx4g", "-XX:+UseG1GC", "-XX:+UseStringDeduplication", "-jar", launcherJar, // ...rest ]) ``` Consider making these configurable via `opencode.json` (e.g., `lsp.jdtls.jvmArgs`). ## Current Workaround Users can work around both issues by disabling the built-in JDTLS and defining a custom LSP server in project-level `opencode.json`: ```json { "lsp": { "jdtls": { "disabled": true }, "java": { "command": ["/path/to/custom-jdtls-script"], "extensions": [".java"], "env": { "JDTLS_WORKSPACE": "/home/user/.cache/jdtls-workspace/my-project" } } } } ``` The custom script sets `-Xms512m -Xmx4g -XX:+UseG1GC` and uses the `JDTLS_WORKSPACE` env var for a persistent `-data` directory. ## Related - #11368 — JDTLS spawns redundant per-module processes (NearestRoot issue) - #12123 — PR fixing the multi-instance root detection ## Environment - opencode v1.1.53 - WSL2 (Ubuntu), Java 21 (Temurin) - Project: 466 pom.xml files, 97 independent Maven module roots
yindo added the perf label 2026-02-16 18:11:29 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: anomalyco/opencode#9038