🧠 Automatic ANTLR DFA Cache Eviction β€” BoxLang 1.17.0

BoxLang 1.17.0 introduces automatic eviction of the ANTLR DFA (Deterministic Finite Automaton) cache, preventing parser memory bloat in long-running deployments. This is a behind-the-scenes improvement β€” everything works exactly as before, just with dramatically lower memory usage and no more OOM errors when pre-compiling large applications on a small JVM.

The Problem

Every time BoxLang parses a source file, ANTLR builds and caches DFA structures to speed up future parses. This cache grows unboundedly β€” it is never reclaimed until the JVM restarts. In long-running deployments with sustained or bursty parsing β€” think web servers, REPL sessions, module reloading during development β€” this cache can consume a lot of heap memory in some cases.

A large BoxLang application can have anywhere from 400MB to 1GB of ANTLR DFA cache sitting in heap. That memory is doing nothing useful after the initial parse, but it’s held forever, forcing unnecessary GC pressure and, in the worst case, causing OutOfMemoryError if you have a very small heap.

The Solution: Three-Trigger Eviction

BoxLang 1.17.0 adds an automatic cache eviction system with three independent triggers:

1. Heap-Pressure Eviction

If the estimated DFA cache size exceeds 1/3 of the max heap, the cache is cleared shortly after the parse that pushed it over the threshold. This prevents OOM during a parse storm β€” the most critical safety net.

2. Idle Eviction

If no parsing has occurred for 3 minutes and the cache is non-empty, it is cleared. Memory is returned to the heap for other work β€” handling requests, running scheduled tasks, whatever your app actually needs.

3. Max-Age Eviction

If the cache exceeds 100 MB and hasn’t been cleared in 10 minutes (even under constant parsing), it is cleared. This prevents silent memory waste during sustained activity.

Design Details

  • Lazy system β€” No threads start and no memory is consumed if parsing never happens (e.g. precompiled deployments). If you never parse, you never pay.
  • Trace logging β€” All evictions produce trace-level log messages to the RUNTIME logger identifying the trigger reason, making diagnostics straightforward if you ever need to investigate.
  • No behavior change β€” This is a purely internal improvement. Your application parses, compiles, and runs identically. The DFA cache is an optimization, not a correctness requirement β€” clearing it just means ANTLR rebuilds structures on the next parse, which is fast.

The Setting

In the unlikely event this causes issues, or you just have unlimited amount of RAM and don’t care, you can opt out via boxlang.json:

{
  "experimental" : {
    // If enabled, the parser DFA cache will be automatically evicted based on heap pressure, idle time, and max age
    "clearParserCache" : false
  }
}

Or via environment variable:

BOXLANG_EXPERIMENTAL_CLEARPARSERCACHE=false

When disabled, the ANTLR DFA cache will never clear, and is only reclaimed on JVM restart.

Quick Start

Nothing to do β€” this is automatic and enabled by default. Deploy 1.17.0 and the cache manages itself. If you ever need the old behavior, set experimental.clearParserCache to false.