BootUI
Try it
Setup
Features
Properties
AI agents
Ecosystem
GitHub
Try it
Setup
Features
Properties
AI agents
Ecosystem
GitHub
  • Get started

    • Try the sample app
    • Setup
    • Spring WebFlux
    • Quarkus
    • Activation and safety
    • Non-standard runtimes
    • Troubleshooting
  • Features

    • All features
    • Overview
    • Advisors
    • Runtime
    • Configuration
    • Database
    • Security
    • Services
    • Diagnostics
    • Developer tools
  • Reference

    • Properties
    • Framework support
    • AI agents
    • Command line
    • BootUI family
  • Diagnostic checks

    • Architecture
    • REST API
    • Spring
    • Hibernate
    • Database
    • Security
    • Vulnerabilities
    • Memory
    • Pentesting
    • GraalVM readiness
    • CRaC readiness
    • Quarkus
    • Quarkus security
  • Contributing

    • Repository
    • Specification
    • Implementation plan
    • Quarkus design notes
    • WebFlux design notes
  • Privacy

Memory checks

The Memory advisor evaluates 32 stable rules against explicit, on-demand JVM observations. Spring MVC, Spring WebFlux, and Quarkus use the same framework-neutral collector, rules, report, and dismissal IDs. MCP and the CLI expose that same report. Reading the cached report does not scan, collect a histogram, or start a recording.

Findings are review prompts, not proof of a leak, a sizing prescription, or a production-readiness assessment. All numerical thresholds below are BootUI heuristics, not Oracle-endorsed universal warning thresholds. Interpret them against representative steady-state and burst workloads.

Reading more than the preview

Rule reports keep ten-entry sampleViolations previews and the full violationCount. View violations and GET <api>/memory/rules/{id}/violations?scanId=...&offset=0&limit=100 read bounded details already retained by that scan: no new histogram, GC, recording, or JVM observation is triggered. Check truncated separately from measurement coverage. See snapshot, retention, and MCP/CLI retrieval. Rules that already count only a top-five input subset keep that bounded scope; paging retrieves the counted sequence, not additional unassessed JVM observations.

Measurement quality, availability, and cost

Missing histogram, pool, maximum, or other required observations do not turn a no-finding result into a passing check. Usable known-findings scores retain these limitations under the shared score eligibility policy.

  • Occupancy is not retained size. MemoryUsage describes a snapshot, including objects that may be unreachable but not yet collected. committed is capacity available to the JVM for that purpose, not process RSS. An undefined maximum (-1) is not proof of unlimited memory. A rule requiring a maximum does not substitute committed capacity, including on collectors whose old-generation committed value equals used.
  • A histogram is not proof of a full GC. The existing explicit GC.class_histogram command requests GC, but HotSpot can skip the requested collection and still produce rows. Allocations can also resume before the subsequent usage reading. The advisor therefore calls these post-histogram snapshots, not verified post-full-GC live sets. A later low snapshot does not explain why usage fell.
  • The histogram is intrusive. Oracle rates its impact High, depending on heap size/content. -all includes unreachable objects and is not a pause-free alternative; BootUI does not switch to it. No additional GC, heap dump, NMT, JFR, subprocess, network request, or background sampling is introduced by these rules. Scan admission is single-flight. Bounded results do not imply a hard execution-time limit for the JVM diagnostic command.
  • Unknown is not zero. Buffer memoryUsed=-1 stays unknown; NIO capacity and estimated used bytes remain distinct. GC count and elapsed time have independent availability. A partial unknown total is not presented as a complete sum. Overflowing estimates become unavailable instead of wrapping into healthy values.
  • Unsupported and failed are different. Ordinary unsupported optional metrics cause the dependent rule to be SKIPPED. Actual thread/histogram supplier or rule failures produce existing ERROR entries in analysisErrors and a PARTIAL scan while retaining unrelated valid findings. Failure to collect the whole context returns ERROR. The results list contains findings, not passing or skipped checks; no findings does not mean every measurement was available or the JVM is healthy.
  • Threads are platform threads. ThreadMXBean excludes virtual threads and cannot detect every deadlock involving them. The existing synchronizer-capability fallback can cover monitors only. Summary counts remain complete when the detail page is capped at 1,000 threads; CPU-hot-thread analysis skips an incomplete detail page. A failed thread report never becomes a passing deadlock check.
  • Histogram bytes are shallow. Totals cover parsed rows; displayed/evaluated class rows are limited to the largest 200, and finding examples remain bounded. Aggregate array/collection observations can consequently undercount the tail. A histogram does not identify one owning collection, retention paths, or retained graphs. HotSpot can also log histogram undercounting separately from its returned rows. HotSpot's synthetic GC filler objects (JDK 19+: jdk.internal.vm.FillerObject and the FillerArray/FillerElement[] filler array) are dead heap space, not application data, so they are excluded from the parsed rows and totals.

For the underlying contracts and collection caveats, see MemoryUsage, MemoryPoolMXBean, GC counters, BufferPoolMXBean, ThreadMXBean, jcmd, and HotSpot's skipped-GC path.

Time and collector boundaries

Recent-GC comparisons span the previous scan's post-histogram counter sample to the current scan's pre-histogram counter sample, excluding the scans' own histogram request intervals. Missing endpoints, non-positive elapsed windows, decreased counters, or changed collector identities cannot become healthy zero activity. A valid zero timer delta is possible even when the collection count increases because timers are approximate milliseconds. Known counts remain usable when time is unknown, and vice versa.

The time ratio is approximate collection elapsed time, not CPU utilization or an exact percentage of application pause time. ZGC/Shenandoah whole-cycle timers and legacy CMS concurrent timing are excluded to avoid adding overlapping cycle and pause time. G1's concurrent manager remains included because its timer covers remark/cleanup VM operations. Collector implementations outside these known conventions need separate interpretation. A completed collection that crosses a sampling boundary can distort a short-window ratio. Lifetime totals still include startup and diagnostic collections.

The direct-buffer and old-generation trends require three strict increases across four comparable observations. A plateau, decrease, missing/unknown reading, or failed whole scan breaks consecutive evidence and requires a new baseline. This measures sampled net growth: releases and allocations may both occur between endpoints. There is no inferred continuous growth, minimum workload, or elapsed-duration guarantee from clicking Scan repeatedly.

JVM/collector scopeInterpretation
JDK 17 through 26 management APIsSnapshot, undefined-max, approximate-counter and resettable-thread-peak semantics remain relevant throughout. Implementation details are not promises for every vendor/update build.
Serial / ParallelOld and young collectors/pools differ. A young collection is not a full old-generation live-set refresh.
G1Reclamation is incremental. G1 Old Gen is a pool; G1 Old Generation is the full-GC manager. A full-GC count does not reveal its cause. Explicit GC and diagnostic requests can contribute.
ZGCNon-generational ZGC exposes one heap. Generational ZGC arrived in 21, became default ZGC in 23, and replaced non-generational ZGC in 24. JDK 26's old-pool committed equals used; that ratio must not be called cap pressure. Generation maxima are not independent capacities to add together.
ShenandoahGenerational mode was experimental in 24 and productized in 25; non-generational remains default through 26. Exposed pools and collection boundaries depend on the actual mode.
Virtual threadsPreviewed in 19/20, finalized in 21. Their heap-backed stacks are not one native -Xss reservation each. Scheduler estimates available in newer JDKs are not a full thread census and are not collected by this advisor.

Sources: GC event timing, G1 manager implementation, Generational ZGC, ZGC default change, non-generational ZGC removal, ZGC pool accounting, Generational Shenandoah experiment, Shenandoah productization, and virtual threads.

Native and container limits

Configured maxima, reserved address space, committed JVM pools, process RSS, and cgroup charges are different measurements. The configured-envelope rule mixes maximum heap, currently committed non-heap, direct-buffer capacity, and approximate platform-stack reservation as an incomplete capacity estimate, not a measured footprint. It excludes GC structures, JIT working memory, native libraries, and non-NIO allocations. A default stack estimate may be used when the effective reservation cannot be read; per-thread reservations and touched pages can differ. Do not add all stack reservations to cgroup current usage: touched stack pages are already charged there.

Container observations come from Linux cgroups. The existing detector resolves the process cgroup, finds the most restrictive finite ancestor limit, and reads leaf current/stat values. This can miss siblings competing for an ancestor's ceiling; it does not prove available headroom. Finite zero-limit detection and hierarchy-wide usage pairing remain detector limitations outside this rule audit. An inactive-file subtraction is a working-set approximation, not guaranteed reclaimability. Current usage includes the cgroup and descendants, not just this JVM.

Operating-system swap statistics describe the operating environment, not this JVM's swapped pages or active paging, so the advisor no longer reports them (retired MEM-FOOTPRINT-004). Comparing the JVM's estimated footprint with currently free physical RAM cannot establish process residency.

NMT is useful confirmation evidence but is disabled by default, requires startup enablement, has documented overhead, and does not account for all native allocations. Its total includes Java Heap; neither its reserved nor committed totals equal RSS. BootUI does not enable NMT or run native-memory commands as part of this advisor. See NMT, OS MXBean, Linux cgroups, and Linux process memory.

Complete rule audit and current behavior

The September 2026 audit retained all 36 IDs. The October 2026 audit (JDK 17-27 HotSpot sources, Oracle JDK 21/26 documentation, and JEPs 421, 439, 474 and 490) retired five noisy rules, fixed five, and added one, leaving 32 active IDs. Each removal, fix and addition was critiqued independently by three different review models; a new rule shipped only with at least two of three in favor. Update means behavior, measurement handling, severity, or diagnostic text changed; Retain means the existing basic heuristic remains; New is an October 2026 addition. Retired IDs are listed in Retired rules and are never reused.

Heap pressure

IDDispositionCurrent trigger, severity, and appropriate action
MEM-HEAP-001UpdateMEDIUM at 95% of a known heap maximum, preferring a valid post-histogram snapshot. Histogram success alone no longer escalates to HIGH or claims retained pressure. Confirm representative pressure before changing heap or retention.
MEM-HEAP-002UpdateMEDIUM at 85% of a known old-pool maximum. Skip absent pools/unknown maxima; never divide by committed instead. Investigate collector-specific occupancy, not an asserted fully collected live set.
MEM-HEAP-003UpdateLOW when max heap is below 15% of a container limit of at least 1 GiB and occupancy is at least 80%. A large limit is not free memory: confirm total native/container headroom before raising heap.
MEM-HEAP-004UpdateINFO when the max heap is at or just above (up to 125% of) the compressed-oops encoding range, which is 4 GiB times ObjectAlignmentInBytes (32 GiB at 8 bytes). HotSpot keeps compressed oops only below that range minus alignment padding, so the common -Xmx32g already disables them; the earlier rule required more than 32 GiB and never reported it. It now prefers the live MaxHeapSize (the Serial/Parallel MXBean maximum excludes a survivor space), live ObjectAlignmentInBytes, and live UseCompressedOops: live true passes, live false within 15/16 to 125% of the range reports, live false well below it is not a cliff. Skips ZGC and an effective (last) -XX:-UseCompressedOops.
MEM-HEAP-005RetainINFO for smaller initial than maximum heap with ZGC/Shenandoah. The collector uses the JVM's reported initial capacity rather than assuming an earlier argument is effective. Equal initial/max may suit latency-sensitive workloads but trades away footprint/uncommit flexibility.
MEM-HEAP-006RetainLOW for at least 1,000 objects pending finalization. Review persistent backlog and resource lifecycle; prefer explicit close/try-with-resources over finalization, deprecated for removal by JEP 421.
MEM-HEAP-008UpdateLOW after three valid increases in old-generation occupancy. Missing observations break the streak. Normal warmup/load changes can explain it; confirm stable load and collector-appropriate reclamation before investigating retention.

Evidence: snapshot contracts, pool semantics, leak investigation, heap-sizing tradeoffs, compressed oops, HotSpot compressed-oops limit, ZGC tuning, and JEP 421.

Native memory

IDDispositionCurrent trigger, severity, and appropriate action
MEM-FOOTPRINT-001UpdateHIGH when known max heap itself meets/exceeds the container limit; otherwise MEDIUM at 90% for the incomplete mixed configured-envelope estimate. Unknown components/overflow cannot become zero. A reservation estimate is not committed or resident pressure.
MEM-FOOTPRINT-002UpdateLOW (was MEDIUM) for approximate platform-stack reservations of at least 1 GiB or 20% of a known container limit. Threads times -Xss is reserved address space; only touched pages are charged to RSS or the cgroup, which MEM-FOOTPRINT-003 measures. Review pool bounds before changing -Xss.
MEM-FOOTPRINT-003RetainHIGH at 90% of the known cgroup limit using current usage or its inactive-file-adjusted working-set estimate. Valid zero usage is not missing. Corroborate hierarchy scope and reclaimability; this is not process RSS.

Evidence: native accounting, OS MXBean scope, cgroup semantics, and process residency.

Memory pools

IDDispositionCurrent trigger, severity, and appropriate action
MEM-POOL-001RetainMEDIUM at 85% of known Metaspace maximum. Undefined maximum/usage is skipped. Pressure can motivate classloader investigation but is not a diagnosed leak.
MEM-POOL-002UpdateMEDIUM at 90% of the whole code cache (unsegmented CodeCache or the sum of all CodeHeap segments), or of the combined profiled + non-profiled nmethod segments. A single full segment is no longer reported: HotSpot falls back non-nmethods → non-profiled → profiled when a segment cannot expand, while nmethods never fall back into non-nmethods. Any undefined segment maximum means not assessed. Saturation constrains new compilation; fragmentation is not measured.
MEM-POOL-003UpdateLOW at 80% of a known effective NIO direct-buffer capacity cap, MEDIUM when the effective (last) argument is -XX:+DisableExplicitGC: before throwing OutOfMemoryError for direct memory, java.nio.Bits.reserveMemory calls System.gc() so cleaners of unreachable buffers run, and that flag makes the call a no-op. Resolve a live HotSpot zero/default option to max heap; otherwise use a known explicit cap or skip. No unknown-to-unlimited inference, mapped-buffer aggregation, or substitution of used bytes for capacity.
MEM-POOL-004UpdateLOW for at least 128 MiB Metaspace with no reported maximum inside a detected memory-limited container. Undefined maximum is not proof of a missing effective cap; setting one can cause Metaspace OOM and cannot guarantee graceful failure.
MEM-POOL-005UpdateMEDIUM at 85% of reported Compressed Class Space maximum. Compressed class pointers are distinct from ordinary object pointers; no universal 1 GiB default is asserted across versions/header modes.
MEM-POOL-007UpdateLOW for three comparable direct-used increases, MEDIUM when capacity is also near its known cap. Missing/unknown samples restart the trend. Net growth cannot establish missing releases or a native leak; use supported library lifecycle APIs, not manual Cleaner calls.

Evidence: pool contracts, buffer estimates, OpenJDK capacity enforcement, OpenJDK default resolution, direct-memory reclamation, code-heap fallback, and VM options.

GC configuration and activity

IDDispositionCurrent trigger, severity, and appropriate action
MEM-GC-001RetainINFO for a detected container without explicit maximum-heap/RAM sizing. HotSpot's approximately 25% default and small-heap ergonomics are context, not a requirement to override them. Recognize -Xmx, MaxHeapSize, MaxRAM, percentage and fraction options.
MEM-GC-002UpdateMEDIUM for approximate lifetime collection time at least 10% of uptime after 10 minutes. Preserve unknown totals/counts independently. Startup and diagnostic collections remain included; do not call this CPU utilization or exact paused time.
MEM-GC-003UpdateMEDIUM at 10% recent approximate collection-time ratio, HIGH at 25%, after a valid interval of at least 10 seconds. Reset/incomparable/unknown endpoints do not yield healthy zero deltas. Corroborate collections crossing the interval boundary.
MEM-GC-004UpdateLOW for Serial GC with at least two processors and roughly 2 GiB of known memory. Unknown memory is not proven server-class capacity; small environments skip. Review workload tradeoffs rather than claiming Serial necessarily wastes resources or causes long pauses.
MEM-GC-005UpdateINFO for a positive comparable G1 Old Generation count delta outside histogram request intervals. A Full GC can be explicit/diagnostic, not necessarily allocation failure. Inspect GC cause/logs before tuning G1.
MEM-GC-006UpdateMEDIUM when the most recently completed event lasted at least 1,000 ms. Select by completion time, not historical maximum duration; suppress an unchanged event from the prior histogram. Concurrent-cycle beans (ZGC/Shenandoah Cycles, legacy ConcurrentMarkSweep) are now excluded before selecting the latest event: their duration spans a whole concurrent cycle and routinely exceeded 1 s, a structural false positive. G1 Concurrent GC stays because it times remark/cleanup pauses.
MEM-GC-007UpdateHIGH when container awareness remains explicitly disabled despite a visible cgroup limit. Respect a later re-enable option. Keep supported-HotSpot and deliberate-override caveats; no automatic sizing changes.
MEM-GC-008NewINFO when ZGC runs in non-generational mode (ZGC Cycles/ZGC Pauses beans) and the live ZGenerational option is readable and false. That combination exists only on JDK 21-22 (default) or JDK 23 with the deprecated -XX:-ZGenerational; the option is absent on 17-20 and obsolete from 24, so the rule cannot fire there. Generational ZGC (JEP 439) usually needs less heap headroom, became the default in JDK 23 (JEP 474), and replaced non-generational ZGC in JDK 24 (JEP 490). Some workloads deliberately prefer the old mode, hence INFO. The JVM Tuning calculator does not emit ZGenerational because it is not portable; this is a runtime observation.

Evidence: GC counters, event timing, G1 full-GC manager, diagnostic full-GC causes, collector tradeoffs, VM options, and JEP 439/JEP 474/JEP 490.

Threads

IDDispositionCurrent trigger, severity, and appropriate action
MEM-THREAD-001UpdateCRITICAL for detected platform-thread deadlock cycles. Missing/failed thread observations are not PASS. Detection covers the supported monitor/synchronizer scope, not all virtual-thread cycles.
MEM-THREAD-002RetainMEDIUM at five BLOCKED threads and 25% of the census, or 20 BLOCKED threads and 10%. Full summary counts support this despite detail paging; a transient snapshot does not prove sustained contention.
MEM-THREAD-004UpdateINFO for currently RUNNABLE platform threads with accumulated CPU at least 60 seconds and half JVM uptime. CPU accumulated before the snapshot is not attributed entirely to its current state. Skip unsupported timing/incomplete detail and confirm with consecutive samples.

Evidence: ThreadMXBean, including peak reset and deadlock support, and virtual-thread scope.

Heap content and class loading

IDDispositionCurrent trigger, severity, and appropriate action
MEM-CONTENT-001RetainINFO for average shallow instance size at least 512 KiB and at least 10 MiB total. An average cannot prove an individual G1 humongous allocation; that also depends on region size.
MEM-CONTENT-002RetainCollection/node rows reaching 50 MiB or 10% shallow share receive LOW; a largest row of 100 MiB or combined selected share of 25% receives MEDIUM. This is not one identified collection, retained size, or proof of missing eviction.
MEM-CONTENT-003RetainLOW when the largest non-array class reaches 25% of total shallow histogram bytes. Arrays are excluded; unexpected retention requires reference-path evidence.
MEM-CLASS-001UpdateINFO at 50,000 currently loaded classes, with framework-generation caveats. Historical unloads no longer exempt a large current population: unloading does not prove health or exclude a leak.
MEM-CLASS-002UpdateINFO at 50,000 lifetime unloads or a lifetime average of 1,000/minute after 30 minutes. A past burst or redeployment can explain the total; it is not sustained recent churn.

Evidence: histogram cost/shape, stable-workload leak investigation, class-loading counters, and optional class unloading.

Retired rules

Retired IDs are never reassigned. Persisted dismissals of a retired ID simply no longer match an active rule.

IDFormer titleRetiredReason
MEM-HEAP-007Committed heap is far above observed heap usageOctober 2026After the histogram request, used heap approximates the live set, and G1's default MinHeapFreeRatio/MaxHeapFreeRatio (40/70) keep committed heap about 1.7-3.3 times that. Equal -Xms/-Xmx, which MEM-HEAP-005 and the JVM Tuning calculator produce, always matched. It flagged healthy GC headroom and admitted it was not a sizing recommendation.
MEM-FOOTPRINT-004High system or environment swap utilizationOctober 2026Host-wide swap is not attributable to this JVM and does not show active paging; macOS dynamic swap files routinely exceed 50% on developer laptops, BootUI's normal context.
MEM-POOL-006JIT compiler is disabled or capped below full optimisationOctober 2026Compiler policy, not a memory signal. IntelliJ's Spring Boot launch optimisation adds -XX:TieredStopAtLevel=1, so it fired on deliberate development configuration. Real code-cache pressure stays covered by MEM-POOL-002.
MEM-THREAD-003Peak thread count was far above the current countOctober 2026A peak since start or the last reset far above the current count is normal pool elasticity after a burst, not a leak or exhaustion. Current stack reservation stays covered by MEM-FOOTPRINT-002.
MEM-CONTENT-004Arrays dominate the sampled heapOctober 2026Arrays at 50% or more of shallow bytes is the ordinary shape of a Java heap (compact-string byte[], collection Object[]); a healthy JVM sample measured 57%. MEM-CONTENT-001 to 003 remain for big objects, collections, and a dominant non-array class.

Considered but not added

  • -XX:+DisableExplicitGC with direct buffers at 50% of their cap (proposed MEM-POOL-008). Two of three reviewers opposed a separate rule: 50% is a normal pooled-buffer steady state and the flag is common, so it would duplicate MEM-POOL-003. The mechanism is instead folded into MEM-POOL-003 as a MEDIUM escalation at its existing 80% threshold.

Confirmation work remains explicit

Use GC logs and comparable workload observations first. For a suspected retention issue, an explicitly requested heap analysis or JFR old-object/root-path investigation can provide stronger evidence; root-path collection can pause the application. For native growth, an already enabled NMT baseline/diff can help but remains incomplete accounting. These are investigation choices, not automatic fixes or additional actions performed by this scan.

No rule was removed solely to improve the advisor score. Severity changes reflect evidence confidence, and dismissals continue to target the same rule IDs. Shared score calculation and incomplete-report presentation are separate concerns.

Prev
Vulnerabilities
Next
Pentesting