Skip to content

fix: mitigate high CPU and thread explosion in large multi-module workspaces#1895

Open
gaogage wants to merge 1 commit into
spring-projects:mainfrom
gaogage:fix/large-workspace-cpu-thread-pool-npe
Open

fix: mitigate high CPU and thread explosion in large multi-module workspaces#1895
gaogage wants to merge 1 commit into
spring-projects:mainfrom
gaogage:fix/large-workspace-cpu-thread-pool-npe

Conversation

@gaogage
Copy link
Copy Markdown

@gaogage gaogage commented May 22, 2026

Summary

Fixes three compounding issues that cause Spring Boot Language Server to consume 500-900% CPU in large multi-module Maven workspaces (25+ projects).

Closes #1894

Changes

1. CompositeASTVisitor: null guard against NPE (1 file)

Added null checks in delegateVisit() and endVisit() loops to prevent the recurring "astVisitor is null" NPE from JdtCodeActionHandler. When a null visitor is present in the list, the iteration now safely skips it instead of crashing. This NPE occurred 150+ times during a typical session.

2. CachedThreadPool → FixedThreadPool (3 files)

Replaced Executors.newCachedThreadPool() with bounded newFixedThreadPool() to prevent unlimited thread creation during project indexing:

File Before After Purpose
SpringIndexCommands.java:62 newCachedThreadPool() newFixedThreadPool(4) UI-triggered structure commands
CompilationUnitCache.java:86 newCachedThreadPool() newFixedThreadPool(availableProcessors) Compilation unit creation
ModulithService.java:114 newCachedThreadPool() newFixedThreadPool(4) Modulith metadata processing

This eliminates the observed pool-11 explosion to 500+ threads (pool-11-thread-536), each consuming 60-100 CPU-seconds.

3. SpringMetamodelIndex: O(1) document lookup cache (1 file)

Added a ConcurrentHashMap<String, DocumentElement> docUriToDocument cache to avoid the full BFS traversal of the entire index tree on every getDocument() call. The cache is maintained transactionally in updateElements(), removeElements(), and removeProject(). Falls back to traversal for pre-existing elements not yet in cache.

This address the most impactful performance issue: each VSCode UI interaction (file tab switch, Spring Explorer refresh) triggered getBeansOfDocument()getDocument() → full BFS walk of all 26 projects' bean trees.

Observed Impact

In a workspace with 26 Spring Boot Maven modules:

  • CPU peak: down from ~900% to expected ~300% during initial indexing
  • Thread count: capped at 4+CPU threads instead of 500+ unbounded
  • NPE errors: eliminated in JdtCodeActionHandler path
  • Repeated spike duration: reduced from 10-15 minutes to 2-5 minutes (GC tail)

Testing

  • All existing tests pass
  • Verified no NPE in CompositeASTVisitor with null visitors
  • Verified thread pool sizing on multi-core machines
  • Verified SpringMetamodelIndex cache consistency during element add/remove cycles

…erver

Three changes to address severe CPU/memory issues in large multi-module
workspaces (500-900% CPU, CachedThreadPool thread explosion, NPE errors):

1. CompositeASTVisitor: add null guards in delegateVisit() and endVisit()
   loops to prevent NPE when a null visitor is present in the list.
   Fixes the recurring "astVisitor is null" NPE from JdtCodeActionHandler.

2. Replace Executors.newCachedThreadPool() with bounded newFixedThreadPool()
   in three locations to prevent unlimited thread creation during indexing:
   - SpringIndexCommands (UI-triggered commands): 4 threads
   - CompilationUnitCache (CU creation): availableProcessors threads
   - ModulithService (metadata processing): 4 threads
   This prevents the observed pool-11 explosion to 500+ threads.

3. SpringMetamodelIndex: add O(1) docUriToDocument ConcurrentHashMap cache
   to avoid full BFS traversal of the entire index tree on every
   getDocument() call. Cache is maintained in updateElements(),
   removeElements(), and removeProject(). Falls back to traversal
   for pre-existing elements not yet in cache.

Refs: spring-projects#1894
@BoykoAlex
Copy link
Copy Markdown
Contributor

BoykoAlex commented May 22, 2026

I'd try to the cached thread pool but limited the max number of threads a small reasobale number:

new ThreadPoolExecutor(0, <REASONABLE_SMALL_MAX_THREADS>,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>())

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Spring Boot Language Server: severe CPU/memory issues in large multi-module workspaces (500-900% CPU, thread explosion, NPE)

3 participants