diff --git a/uimaj-core/src/main/java/org/apache/uima/cas/impl/FSClassRegistry.java b/uimaj-core/src/main/java/org/apache/uima/cas/impl/FSClassRegistry.java index 472bdedae..aad9103b8 100644 --- a/uimaj-core/src/main/java/org/apache/uima/cas/impl/FSClassRegistry.java +++ b/uimaj-core/src/main/java/org/apache/uima/cas/impl/FSClassRegistry.java @@ -21,6 +21,7 @@ import static java.lang.invoke.MethodHandles.lookup; import static java.lang.invoke.MethodType.methodType; +import static java.util.Collections.emptyMap; import java.lang.invoke.LambdaMetafactory; import java.lang.invoke.MethodHandle; @@ -393,7 +394,7 @@ private static void loadBuiltins(TypeImpl ti, ClassLoader cl, if (BuiltinTypeKinds.creatableBuiltinJCas.contains(typeName) || typeName.equals(CAS.TYPE_NAME_SOFA)) { - JCasClassInfo jcci = getOrCreateJCasClassInfo(ti, cl, type2jcci, defaultLookup); + JCasClassInfo jcci = getOrCreateJCasClassInfo(ti, cl, type2jcci, emptyMap()); assert jcci != null; // done while beginning to commit the staticTsi (before committed flag is set), for builtins updateOrValidateAllCallSitesForJCasClass(jcci.jcasClass, ti, callSites_toSync); @@ -481,12 +482,12 @@ private static synchronized void loadJCasForTSandClassLoader(TypeSystemImpl ts, */ // @formatter:on - ArrayList callSites_toSync = new ArrayList<>(); + var spiJCasClasses = loadJCasClassesFromSPI(cl); + var callSites_toSync = new ArrayList(); maybeLoadJCasAndSubtypes(ts, ts.topType, type2jcci.get(TOP.class.getCanonicalName()), cl, - type2jcci, callSites_toSync); + type2jcci, callSites_toSync, spiJCasClasses); - MutableCallSite[] sync = callSites_toSync - .toArray(new MutableCallSite[callSites_toSync.size()]); + var sync = callSites_toSync.toArray(new MutableCallSite[callSites_toSync.size()]); MutableCallSite.syncAll(sync); checkConformance(ts, ts.topType, type2jcci); @@ -531,9 +532,10 @@ private static void setTypeFromJCasIDforBuiltIns(JCasClassInfo jcci, TypeSystemI // @formatter:on private static void maybeLoadJCasAndSubtypes(TypeSystemImpl tsi, TypeImpl ti, JCasClassInfo copyDownDefault_jcasClassInfo, ClassLoader cl, - Map type2jcci, ArrayList callSites_toSync) { + Map type2jcci, ArrayList callSites_toSync, + Map> aSpiJCasClasses) { - var jcci = getOrCreateJCasClassInfo(ti, cl, type2jcci, null); + var jcci = getOrCreateJCasClassInfo(ti, cl, type2jcci, aSpiJCasClasses); if (null != jcci && tsi.isCommitted()) { updateOrValidateAllCallSitesForJCasClass(jcci.jcasClass, ti, callSites_toSync); @@ -623,7 +625,8 @@ private static void maybeLoadJCasAndSubtypes(TypeSystemImpl tsi, TypeImpl ti, } for (var subType : ti.getDirectSubtypes()) { - maybeLoadJCasAndSubtypes(tsi, subType, jcci_or_copyDown, cl, type2jcci, callSites_toSync); + maybeLoadJCasAndSubtypes(tsi, subType, jcci_or_copyDown, cl, type2jcci, callSites_toSync, + aSpiJCasClasses); } } @@ -638,17 +641,36 @@ private static void maybeLoadJCasAndSubtypes(TypeSystemImpl tsi, TypeImpl ti, * * @return jcci or null, if no JCas class for this type was able to be loaded * - * @deprecated This will become a package private method. + * @deprecated This will be removed without replacement * @forRemoval 4.0.0 */ @Deprecated(since = "3.5.1") public static JCasClassInfo getOrCreateJCasClassInfo(TypeImpl aTypeInfo, ClassLoader aClassLoader, Map aType2Jcci, Lookup aUnusedLookup) { + return getOrCreateJCasClassInfo(aTypeInfo, aClassLoader, aType2Jcci, + loadJCasClassesFromSPI(aClassLoader)); + } + + /** + * For a particular type name, get the JCasClassInfo + *
    + *
  • by fetching the cached value + *
  • by loading the class + *
  • return null if no JCas class for this name + *
+ * only called for non-Pear callers + * + * @return jcci or null, if no JCas class for this type was able to be loaded + */ + static JCasClassInfo getOrCreateJCasClassInfo(TypeImpl aTypeInfo, ClassLoader aClassLoader, + Map aType2Jcci, + Map> aSpiJCasClasses) { + var jcci = aType2Jcci.get(aTypeInfo.getJCasClassName()); if (jcci == null) { - jcci = maybeCreateJCasClassInfo(aTypeInfo, aClassLoader, aType2Jcci, aUnusedLookup); + jcci = maybeCreateJCasClassInfo(aTypeInfo, aClassLoader, aType2Jcci, aSpiJCasClasses); } // do this setup for new type systems using previously loaded jcci, as well as @@ -665,10 +687,10 @@ public static JCasClassInfo getOrCreateJCasClassInfo(TypeImpl aTypeInfo, ClassLo */ @Deprecated(since = "3.5.1") static JCasClassInfo maybeCreateJCasClassInfo(TypeImpl ti, ClassLoader cl, - Map type2jcci, Lookup aUnusedLookup) { + Map type2jcci, Map> aSpiJCasClasses) { // does update of callsites if was able find JCas class - var jcci = createJCasClassInfo(ti, cl, aUnusedLookup); + var jcci = createJCasClassInfo(ti, cl, aSpiJCasClasses); if (null != jcci) { type2jcci.put(ti.getJCasClassName(), jcci); @@ -678,14 +700,19 @@ static JCasClassInfo maybeCreateJCasClassInfo(TypeImpl ti, ClassLoader cl, } /** - * @deprecated This will become a private method. + * @deprecated This will be removed without replacement. * @forRemoval 4.0.0 */ @SuppressWarnings("javadoc") @Deprecated(since = "3.5.1") public static JCasClassInfo createJCasClassInfo(TypeImpl aTypeInfo, ClassLoader aClassLoader, Lookup aUnusedLookup) { - var jcasClass = maybeJCasWrapperClass(aTypeInfo, aClassLoader); + return createJCasClassInfo(aTypeInfo, aClassLoader, loadJCasClassesFromSPI(aClassLoader)); + } + + private static JCasClassInfo createJCasClassInfo(TypeImpl aTypeInfo, ClassLoader aClassLoader, + Map> aSpiJCasClasses) { + var jcasClass = maybeJCasWrapperClass(aTypeInfo, aClassLoader, aSpiJCasClasses); if (null == jcasClass || !TOP.class.isAssignableFrom(jcasClass)) { return null; } @@ -705,7 +732,6 @@ public static JCasClassInfo createJCasClassInfo(TypeImpl aTypeInfo, ClassLoader var lookup = getLookup(jcasClass.getClassLoader()); return createJCasClassInfo(jcasClass, aTypeInfo, jcasType, lookup); } - // static AtomicLong time = IS_TIME_AUGMENT_FEATURES ? new AtomicLong(0) : null; // // static { @@ -957,7 +983,8 @@ private static String getAllSuperTypeNames(TypeImpl ti) { * @return the loaded / resolved class */ @SuppressWarnings("unchecked") - private static Class maybeJCasWrapperClass(TypeImpl ti, ClassLoader cl) { + private static Class maybeJCasWrapperClass(TypeImpl ti, ClassLoader cl, + Map> aSpiJCasClasses) { var className = ti.getJCasClassName(); // First we try the local classloader - this is necessary because it might be a PEAR situation @@ -970,7 +997,7 @@ private static Class maybeJCasWrapperClass(TypeImpl ti, ClassLoad } // If the local classloader does not have the JCas wrapper, we try the SPI - return loadJCasClassesFromSPI(cl).get(className); + return aSpiJCasClasses.get(className); } static Map> loadJCasClassesFromSPI(ClassLoader cl) { diff --git a/uimaj-core/src/main/java/org/apache/uima/cas/impl/TypeSystemImpl.java b/uimaj-core/src/main/java/org/apache/uima/cas/impl/TypeSystemImpl.java index d82cce16c..644184461 100644 --- a/uimaj-core/src/main/java/org/apache/uima/cas/impl/TypeSystemImpl.java +++ b/uimaj-core/src/main/java/org/apache/uima/cas/impl/TypeSystemImpl.java @@ -1479,9 +1479,11 @@ private TypeSystemImpl finalizeCommit(ClassLoader cl) { type2jcci = FSClassRegistry.get_className_to_jcci(cl, false /* is not PEAR */); lookup = FSClassRegistry.getLookup(cl); + var spiJCasClasses = FSClassRegistry.loadJCasClassesFromSPI(cl); cl_for_commit = cl; - computeAdjustedFeatureOffsets(topType); // must preceed the FSClassRegistry JCas stuff below + computeAdjustedFeatureOffsets(topType, spiJCasClasses); // must preceed the FSClassRegistry JCas + // stuff below // Load all the available JCas classes (if not already loaded). // Has to follow above, because information computed above is used when @@ -1529,7 +1531,8 @@ private TypeSystemImpl finalizeCommit(ClassLoader cl) { * - the type */ //@formatter:on - private void computeAdjustedFeatureOffsets(TypeImpl ti) { + private void computeAdjustedFeatureOffsets(TypeImpl ti, + Map> aSpiJCasClasses) { List tempIntFis = new ArrayList<>(); List tempRefFis = new ArrayList<>(); @@ -1549,10 +1552,10 @@ private void computeAdjustedFeatureOffsets(TypeImpl ti) { // this includes any superClass of a jcas class which is not in this type system if (!skip_loading_user_jcas) { - JCasClassInfo jcci = getOrCreateJcci(ti); + JCasClassInfo jcci = getOrCreateJcci(ti, aSpiJCasClasses); if (jcci != null) { - addJCasOffsetsWithSupers(jcci.jcasClass, tempIntFis, tempRefFis, tempNsr); + addJCasOffsetsWithSupers(jcci.jcasClass, tempIntFis, tempRefFis, tempNsr, aSpiJCasClasses); } } @@ -1582,7 +1585,7 @@ private void computeAdjustedFeatureOffsets(TypeImpl ti) { // ti.hasNoSlots = ti.nbrOfUsedIntDataSlots == 0 && ti.nbrOfUsedRefDataSlots == 0; for (TypeImpl sub : ti.getDirectSubtypes()) { - computeAdjustedFeatureOffsets(sub); + computeAdjustedFeatureOffsets(sub, aSpiJCasClasses); } } @@ -1610,7 +1613,8 @@ private void computeAdjustedFeatureOffsets(TypeImpl ti) { */ //@formatter:on private void addJCasOffsetsWithSupers(Class clazz, List tempIntFis, - List tempRefFis, List tempNsrFis) { + List tempRefFis, List tempNsrFis, + Map> aSpiJCasClasses) { Class superClass = clazz.getSuperclass(); // ********************** @@ -1634,7 +1638,7 @@ private void addJCasOffsetsWithSupers(Class clazz, List tempIntF String uimaTypeName = Misc.javaClassName2UimaTypeName(className); TypeImpl ti = getType(uimaTypeName); if (ti != null) { - maybeAddJCasOffsets(ti, tempIntFis, tempRefFis, tempNsrFis); + maybeAddJCasOffsets(ti, tempIntFis, tempRefFis, tempNsrFis, aSpiJCasClasses); } return; } @@ -1643,7 +1647,7 @@ private void addJCasOffsetsWithSupers(Class clazz, List tempIntF // If they exist, they will have been loaded/created by FSClassRegistry.augmentFeaturesFromJCas // some intermediate superclasses may not have jccis, just skip over them. - addJCasOffsetsWithSupers(superClass, tempIntFis, tempRefFis, tempNsrFis); + addJCasOffsetsWithSupers(superClass, tempIntFis, tempRefFis, tempNsrFis, aSpiJCasClasses); // maybeAddJCasOffsets(clazz, tempIntFis, tempRefFis, tempNsrFis); // already done by above // statement } @@ -1661,9 +1665,10 @@ private void addJCasOffsetsWithSupers(Class clazz, List tempIntF * list to augment with additional slots */ private void maybeAddJCasOffsets(TypeImpl ti, List tempIntFis, - List tempRefFis, List tempNsrFis) { + List tempRefFis, List tempNsrFis, + Map> aSpiJCasClasses) { - JCasClassInfo jcci = getOrCreateJcci(ti); + JCasClassInfo jcci = getOrCreateJcci(ti, aSpiJCasClasses); if (null != jcci) { // could be null if class is not a JCas class addJCasOffsets(jcci, tempIntFis, tempRefFis, tempNsrFis); } @@ -1735,8 +1740,9 @@ void setOffset2Feat(List tempIntFis, List tempRefFis, } } - private JCasClassInfo getOrCreateJcci(TypeImpl ti) { - return FSClassRegistry.getOrCreateJCasClassInfo(ti, cl_for_commit, type2jcci, lookup); + private JCasClassInfo getOrCreateJcci(TypeImpl ti, + Map> aSpiJCasClasses) { + return FSClassRegistry.getOrCreateJCasClassInfo(ti, cl_for_commit, type2jcci, aSpiJCasClasses); } JCasClassInfo getJcci(String typeName) { diff --git a/uimaj-core/src/test/java/org/apache/uima/cas/impl/TypeSystemImplTest.java b/uimaj-core/src/test/java/org/apache/uima/cas/impl/TypeSystemImplTest.java index ebb72e14a..a6edee853 100644 --- a/uimaj-core/src/test/java/org/apache/uima/cas/impl/TypeSystemImplTest.java +++ b/uimaj-core/src/test/java/org/apache/uima/cas/impl/TypeSystemImplTest.java @@ -18,6 +18,7 @@ */ package org.apache.uima.cas.impl; +import static java.lang.System.currentTimeMillis; import static org.apache.uima.UIMAFramework.getResourceSpecifierFactory; import static org.apache.uima.util.CasCreationUtils.createCas; import static org.assertj.core.api.Assertions.assertThat; @@ -25,6 +26,7 @@ import static org.assertj.core.api.Assertions.assertThatNoException; import static org.assertj.core.api.InstanceOfAssertFactories.throwable; +import java.lang.invoke.MethodHandles; import java.lang.management.ManagementFactory; import org.apache.uima.cas.CAS; @@ -36,12 +38,16 @@ import org.apache.uima.resource.metadata.TypeSystemDescription; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import x.y.z.Sentence; class TypeSystemImplTest { private TypeSystemDescription tsd; + static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + @BeforeEach void setup() { tsd = getResourceSpecifierFactory().createTypeSystemDescription(); @@ -109,7 +115,16 @@ void testMetaspaceExhaustion() throws Exception { var type = tsd.addType(Sentence._TypeName, "", CAS.TYPE_NAME_ANNOTATION); type.addFeature(Sentence._FeatName_sentenceLength, null, CAS.TYPE_NAME_INTEGER); + LOG.info("Metaspace exhaustion test starting - this may take a while..."); + var startTime = currentTimeMillis(); + var target = threshold * 2; for (var i = 0; i < threshold * 2; i++) { + if ((i + 1) % 250 == 0) { + var duration = currentTimeMillis() - startTime; + LOG.info("Metaspace exhaustion test - progress: {} / {} -- {}ms per CAS", i + 1, target, + duration / i + 1); + } + var resMgr = new ResourceManager_impl(); resMgr.setExtensionClassPath(".", false); createCas(tsd, null, null, null, resMgr).getJCas(); @@ -121,5 +136,10 @@ void testMetaspaceExhaustion() throws Exception { .as("High number of new loaded classes during test indicates leak") .isLessThan(classesLoadedAtStart + threshold); } + LOG.info("Classes accumulated during test: {}", + classLoadingMXBean.getLoadedClassCount() - classesLoadedAtStart); + var duration = currentTimeMillis() - startTime; + LOG.info("Metaspace exhaustion test finished in {}ms ({}ms per CAS)", duration, + duration / target); } } diff --git a/uimaj-core/src/test/resources/log4j2-test.xml b/uimaj-core/src/test/resources/log4j2-test.xml index 848041694..1cbdb7ce0 100644 --- a/uimaj-core/src/test/resources/log4j2-test.xml +++ b/uimaj-core/src/test/resources/log4j2-test.xml @@ -25,6 +25,8 @@ + +