When debug a oom issue on new version of application. I noticed that
Old version:
New version:
The classes increased almost twice compare to the old version.
So I just export loaded classes to figure out what classes increased at first.
After a compare, we got a obvious increased class:
for the new version
about 25000 Doc classes is loaded and 45439 + 25000 = 70439 seems very near to 74.3k.
So check the heap dump by Dominator tree, by comparing heap usage, one class come into view is that:
Code reading
ZStack use reflections to get information and offer framework level capabilities.
1 2 3
publicstatic Reflections reflections = new Reflections(ClasspathHelper.forPackage("org.zstack"), new SubTypesScanner(), new MethodAnnotationsScanner(), new FieldAnnotationsScanner(), new TypeAnnotationsScanner(), new MethodParameterScanner());
/** created new SubTypesScanner. will exclude direct Object subtypes */ publicSubTypesScanner(){ this(true); //exclude direct Object subtypes by default }
/** created new SubTypesScanner. * @param excludeObjectClass if false, include direct {@link Object} subtypes in results. */ publicSubTypesScanner(boolean excludeObjectClass){ if (excludeObjectClass) { filterResultsBy(new FilterBuilder().exclude(Object.class.getName())); //exclude direct Object subtypes } }
if (acceptResult(superclass)) { getStore().put(superclass, className); }
for (String anInterface : (List<String>) getMetadataAdapter().getInterfacesNames(cls)) { if (acceptResult(anInterface)) { getStore().put(anInterface, className); } } } }
MethodAnnotationsScanner
1 2 3 4 5 6 7 8 9 10 11
publicclassMethodAnnotationsScannerextendsAbstractScanner{ publicvoidscan(final Object cls){ for (Object method : getMetadataAdapter().getMethods(cls)) { for (String methodAnnotation : (List<String>) getMetadataAdapter().getMethodAnnotationNames(method)) { if (acceptResult(methodAnnotation)) { getStore().put(methodAnnotation, getMetadataAdapter().getMethodFullKey(cls, method)); } } } } }
TypeAnnotationsScanner
1 2 3 4 5 6 7 8 9 10 11 12 13 14
publicclassTypeAnnotationsScannerextendsAbstractScanner{ publicvoidscan(final Object cls){ final String className = getMetadataAdapter().getClassName(cls);
for (String annotationType : (List<String>) getMetadataAdapter().getClassAnnotationNames(cls)) {
if (acceptResult(annotationType) || annotationType.equals(Inherited.class.getName())) { //as an exception, accept Inherited as well getStore().put(annotationType, className); } } }
/** scan type superclasses and interfaces * <p></p> * <i>Note that {@code Object} class is excluded by default, in order to reduce store size. * <br>Use {@link #filterResultsBy(Predicate)} to change, for example {@code SubTypes.filterResultsBy(c -> true)}</i> * */ SubTypes { /* Object class is excluded by default from subtypes indexing */ { filterResultsBy(new FilterBuilder().excludePattern("java\\.lang\\.Object")); }
almost same logic is supported but check details about the scan() method
1 2 3 4 5 6 7 8 9 10 11 12 13 14
for (Scanner scanner : configuration.getScanners()) { try { if (doFilter(file, scanner::acceptsInput)) { List<Map.Entry<String, String>> entries = scanner.scan(file); if (entries == null) { if (classFile == null) classFile = getClassFile(file); entries = scanner.scan(classFile); } if (entries != null) collect.get(scanner.index()).addAll(entries); } } catch (Exception e) { if (log != null) log.trace("could not scan file {} with scanner {}", file.getRelativePath(), scanner.getClass().getSimpleName(), e); } }
acceptsInput will be used to do filter which check file end with .class suffix but filterResultsBy is not executed only if entries = scanner.scan(classFile) is used.
So when List<Map.Entry<String, String>> entries = scanner.scan(file); return entries the result won’t be exclude.
Hands-on test
I set up a maven project to test reflections issue with a project:
// reflections 0.9.10 Reflections reflections = new Reflections(ClasspathHelper.forPackage("org.zstack"), new SubTypesScanner(false), new MethodAnnotationsScanner(), new FieldAnnotationsScanner(), new TypeAnnotationsScanner(), new MethodParameterScanner());
// reflections 0.10.2 // Reflections reflections = new Reflections(ClasspathHelper.forPackage("org.zstack"), // new SubTypesScanner(false), new MethodAnnotationsScanner(), new FieldAnnotationsScanner(), // new TypeAnnotationsScanner(), new MethodParameterScanner());
if (acceptResult(superclass)) { getStore().put(superclass, className); }
for (String anInterface : (List<String>) getMetadataAdapter().getInterfacesNames(cls)) { if (acceptResult(anInterface)) { getStore().put(anInterface, className); } }
0.9.10 will filter the superclass before put it into reflections store but 0.10.2 use it directly.
in 0.9.10 scan works like following:
1 2 3 4 5 6 7 8 9 10 11 12 13
if (inputsFilter == null || inputsFilter.apply(path) || inputsFilter.apply(fqn)) { Object classObject = null; for (Scanner scanner : configuration.getScanners()) { try { if (scanner.acceptsInput(path) || scanner.acceptResult(fqn)) { classObject = scanner.scan(file, classObject); } } catch (Exception e) { if (log != null && log.isDebugEnabled()) log.debug("could not scan file " + file.getRelativePath() + " in url " + url.toExternalForm() + " with scanner " + scanner.getClass().getSimpleName(), e.getMessage()); } } }
scanner will check fqn of class first and then check the interface but in 0.10.2 version:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
for (Scanner scanner : configuration.getScanners()) { try { if (doFilter(file, scanner::acceptsInput)) { List<Map.Entry<String, String>> entries = scanner.scan(file); if (entries == null) { if (classFile == null) classFile = getClassFile(file); entries = scanner.scan(classFile); } if (entries != null) collect.get(scanner.index()).addAll(entries); } } catch (Exception e) { if (log != null) log.trace("could not scan file {} with scanner {}", file.getRelativePath(), scanner.getClass().getSimpleName(), e); } }
File will be checked before scan by Filter but superclass’s belonging is not checked which seems to blame.
But actually, groovy closure does not extend Object but GroovyObject, so reflections will still load groovy closures. So check reflections getAllTypes()
for 0.9.10
1 2 3 4 5 6 7 8
public Set<String> getAllTypes(){ Set<String> allTypes = Sets.newHashSet(store.getAll(index(SubTypesScanner.class), Object.class.getName())); if (allTypes.isEmpty()) { thrownew ReflectionsException("Couldn't find subtypes of Object. " + "Make sure SubTypesScanner initialized to include Object class - new SubTypesScanner(false)"); } return allTypes; }
All most same but in 0.9.10 Object related types will be returned. So as a result, only java objects is returned.
Work around on 0.10.2 can use following codes:
1 2 3 4 5 6 7 8 9 10
ConfigurationBuilder builder = ConfigurationBuilder.build() .setUrls(ClasspathHelper.forPackage("org.zstack")) .setScanners(new SubTypesScanner(false), new MethodAnnotationsScanner(), new FieldAnnotationsScanner(), new TypeAnnotationsScanner(), new MethodParameterScanner()) .setExpandSuperTypes(false) .filterInputsBy(new FilterBuilder().includePackage("org.zstack")); Reflections reflections = new Reflections(builder);
filterInputsBy will filter class not in package org.zstack and setExpandSuperTypes will ignore super types of scanned result.
Note: but the result still contains groovy closure even its count cut down to acceptable numbers.