Merge pull request #171 from apache/bugfix/UIMA-6413-Memory-leak-in-FSClassRegistry
[UIMA-6413] Memory leak in FSClassRegistry
diff --git a/uimaj-core/src/main/java/org/apache/uima/internal/util/UIMAClassLoader.java b/uimaj-core/src/main/java/org/apache/uima/internal/util/UIMAClassLoader.java
index 983b59b..f3c4f99 100644
--- a/uimaj-core/src/main/java/org/apache/uima/internal/util/UIMAClassLoader.java
+++ b/uimaj-core/src/main/java/org/apache/uima/internal/util/UIMAClassLoader.java
@@ -29,6 +29,7 @@
import java.util.StringTokenizer;
import org.apache.uima.cas.impl.FSClassRegistry;
+import org.apache.uima.cas.impl.TypeSystemImpl;
/**
* UIMAClassLoader is used as extension ClassLoader for UIMA to load additional components like
@@ -284,7 +285,16 @@
@Override
public void close() throws IOException {
isClosed = true;
- FSClassRegistry.unregister_jcci_classloader(this);
+ // There is a circular dependency between the static initializer blocks of FSClassRegistry and
+ // TypeSystemImpl which requires that the TypeSystemImpl class must be initialized before the
+ // FSClassRegistry to avoid exceptions. The if-statement here is a red-herring because the
+ // actual comparison does not really matter - under normal circumstances, `staticTsi` cannot be
+ // null.
+ // However, what it really does is trigger the static initialization block of TypeSystemImpl
+ // so that the subsequent call to FSClassRegistry does not trigger an exception.
+ if (TypeSystemImpl.staticTsi != null) {
+ FSClassRegistry.unregister_jcci_classloader(this);
+ }
super.close();
}
diff --git a/uimaj-core/src/test/java/org/apache/uima/cas/impl/FSClassRegistryTest.java b/uimaj-core/src/test/java/org/apache/uima/cas/impl/FSClassRegistryTest.java
index 3f04259..78379f9 100644
--- a/uimaj-core/src/test/java/org/apache/uima/cas/impl/FSClassRegistryTest.java
+++ b/uimaj-core/src/test/java/org/apache/uima/cas/impl/FSClassRegistryTest.java
@@ -32,13 +32,18 @@
@Before
public void setup() {
System.setProperty(FSClassRegistry.RECORD_JCAS_CLASSLOADERS, "true");
+
+ // Calls to FSClassRegistry will fail unless the static initializer block in TypeSystemImpl
+ // has previously been triggered! During normal UIMA operations, this should not happen,
+ // in particular because FSClassRegistry is not really part of the public UIMA API -
+ // but in the minimal setup here, we need to make sure TypeSystemImpl has been initialized
+ // first.
+ new TypeSystemImpl();
}
@Test
public void thatCreatingResourceManagersWithExtensionClassloaderDoesNotFillUpCache()
throws Exception {
- // Needed to get the type system code initialized before we call clToType2JCasSize();
- CasCreationUtils.createCas();
int numberOfCachedClassloadersAtStart = FSClassRegistry.clToType2JCasSize();
for (int i = 0; i < 5; i++) {
ResourceManager resMgr = UIMAFramework.newDefaultResourceManager();
@@ -60,8 +65,6 @@
@Test
public void thatCreatingResourceManagersWithExtensionPathDoesNotFillUpCache() throws Exception {
- // Needed to get the type system code initialized before we call clToType2JCasSize();
- CasCreationUtils.createCas();
int numberOfCachedClassloadersAtStart = FSClassRegistry.clToType2JCasSize();
for (int i = 0; i < 5; i++) {
ResourceManager resMgr = UIMAFramework.newDefaultResourceManager();
diff --git a/uimaj-core/src/test/java/org/apache/uima/resource/impl/ResourceManager_implTest.java b/uimaj-core/src/test/java/org/apache/uima/resource/impl/ResourceManager_implTest.java
index 3b6d58a..2aa2150 100644
--- a/uimaj-core/src/test/java/org/apache/uima/resource/impl/ResourceManager_implTest.java
+++ b/uimaj-core/src/test/java/org/apache/uima/resource/impl/ResourceManager_implTest.java
@@ -19,6 +19,7 @@
package org.apache.uima.resource.impl;
+import static org.assertj.core.api.Assertions.assertThatCode;
import static org.junit.Assert.assertTrue;
import java.io.File;
@@ -266,4 +267,13 @@
JUnitExtension.handleException(e);
}
}
+
+ @Test
+ public void testCreateWithExtensionClassloaderAndDestroy() throws Exception {
+ assertThatCode(() -> {
+ ResourceManager resMgr = UIMAFramework.newDefaultResourceManager();
+ resMgr.setExtensionClassLoader(getClass().getClassLoader(), false);
+ resMgr.destroy();
+ }).doesNotThrowAnyException();
+ }
}