Cache NetworkClassLoader in server scope to avoid redundant reloads#30
Merged
Merged
Conversation
Uses a versioned server-scope key (matching URLClassLoader pattern) and a double-checked lock to reuse an existing NetworkClassLoader instance across requests, preventing repeated class path scanning on every JavaLoader init, and NetworkClassLoader instances accumulating in JVM Metaspace.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Uses a versioned server-scope key (matching URLClassLoader pattern) and a double-checked lock to reuse an existing NetworkClassLoader instance across requests, preventing repeated class path scanning on every JavaLoader init, and NetworkClassLoader instances accumulating in JVM Metaspace.
Description
Problem
On ColdFusion/Lucee applications using ColdBox, every application reinit causes a new NetworkClassLoader instance to be created and orphaned in JVM memory, never to be garbage collected.
Root cause: Loader.cfc's setup() method correctly checks server scope to avoid recreating the JavaLoader instance on reinit. However, when the JavaLoader already exists in server scope, the else branch calls init() on it to reconfigure it:
JavaLoader.init() unconditionally calls loadClasses(), which unconditionally creates a brand new NetworkClassLoader instance on every invocation with no check for an existing one:
While ensureNetworkClassLoaderOnServerScope() correctly caches the underlying URLClassLoader in server scope and reuses it across reinits, loadClasses() wraps it in a new NetworkClassLoader every time. The old NetworkClassLoader instances are never closed and cannot be garbage collected because their threads are GC roots, causing them to accumulate in JVM Metaspace indefinitely.
Impact
On a busy application with frequent deploys (which trigger reinits), this causes:
This can be observed by running:
jcmd <PID> VM.classloaders 2>&1 | grep -c "NetworkClassLoader"A healthy instance should show 1. A leaking instance will show a count matching the number of reinits since the last JVM restart.
Fix
Cache the NetworkClassLoader in server scope using the same double-checked lock pattern already used by ensureNetworkClassLoaderOnServerScope() for the URLClassLoader. On subsequent reinits, loadClasses() detects the cached instance, sets it as the active classloader, and returns early without creating a new one.
The cache key uses the same uuid + version base as the existing server scope key with a .networkclassloader suffix to avoid collision with the URLClassLoader entry.
Result
After this fix, NetworkClassLoader count remains stable at 1 across any number of application reinits for the lifetime of the JVM, and Metaspace growth from classloader accumulation is eliminated entirely.
Issues
Sorry - I do not see in Forgebox where the bug tracker for this module is located.
Type of change
Please delete options that are not relevant.