Our plugin cannot be dynamically unloaded due to a classloader leak that appears to originate from IntelliJ platform internals, not our plugin code.
Memory Leak Pattern:
ROOT: Global JNI
└── java.util.TimerThread
└── inheritedAccessControlContext: java.security.AccessControlContext
└── context: java.security.ProtectionDomain[]
└── ProtectionDomain
└── classloader: com.intellij.ide.plugins.cl.PluginClassLoader
Logs:
2025-08-27 15:24:42,915 [ 46664] INFO - #c.i.o.p.UnindexedFilesScannerExecutor - [3e6fb5e] Task finished (scanning id=1): com.intellij.util.indexing.UnindexedFilesScannerExecutorImpl$ScheduledScanningTask@58f1f07
2025-08-27 15:24:42,915 [ 46664] INFO - #c.i.u.i.d.ProjectIndexingDependenciesService - Complete token: com.intellij.util.indexing.dependencies.IncompleteTaskToken@28d85479, successful: true
2025-08-27 15:24:43,462 [ 47211] INFO - #c.i.i.p.DynamicPlugins - Snapshot analysis result: Root 1:
ROOT: Global JNI
(root): java.util.TimerThread
inheritedAccessControlContext: java.security.AccessControlContext
context: java.security.ProtectionDomain[]
[]: java.security.ProtectionDomain
* classloader: com.intellij.ide.plugins.cl.PluginClassLoader
2025-08-27 15:24:43,498 [ 47247] INFO - #c.i.i.p.DynamicPlugins - Plugin dev.sweep.assistant is not unload-safe because class loader cannot be unloaded. Memory snapshot created at /Users/kevinlu/unload-dev.sweep.assistant-27.08.2025_15.24.40.hprof
2025-08-27 15:24:43,545 [ 47294] INFO - #c.i.o.p.DumbServiceImpl - enter dumb mode [autocomplete-playground]
2025-08-27 15:24:43,551 [ 47300] SEVERE - #c.i.i.p.PluginManager - getService(...) must not be null
java.lang.NullPointerException: getService(...) must not be null
2025-08-27 15:24:43,551 [ 47300] SEVERE - #c.i.i.p.PluginManager - IntelliJ IDEA 2025.1 Build #IC-251.23774.435
Key Details:
- The TimerThread has no clear parent caller or owner in the reference chain
- The leak appears to be caused by IntelliJ’s internal systems (image loading, icon caching) capturing our plugin’s security context during background operations
- Our plugin follows all documented best practices for dynamic plugin support and properly implements
Disposable - All plugin services are correctly disposed, but IntelliJ internals still hold references
Environment:
- The leak prevents dynamic plugin reloading and requires IDE restart
This appears to be a platform issue where IntelliJ’s internal Timer/background systems capture plugin security contexts, but no documented solution exists for this specific leak pattern.
Also posted on YouTrack: https://youtrack.jetbrains.com/issue/IJPL-204652/Dynamic-Plugin-Unload-Fails-TimerThread-with-Global-JNI-Root-Holds-PluginClassLoader-via-AccessControlContext


