Enforce limit on classes loaded by Spel compiled expression loader
Until this change a single classloader was used to load all compiled SpEL expressions. This meant in a context where an expression was repeatedly flipping between compiled and interpreted mode (which can happen if in MIXED mode compilation and changing the context around the evaluation) the classloader would continually load a new compiled version but not orphan the old compiled version. This eventually uses up all the memory as the number of classes is ever increasing. With this change classloaders are used to load 100 compiled expressions. The 101st will be loaded by a new one. Orphaning the old classloader means if an expression is ever recompiled there is more likely to be no anchored references left to the older compiled form and it can be GC'd. In the MIXED situation above it should help alleviate the problem of older classes never being candidates for GC. Issue: SPR-15460
This commit is contained in:
@@ -22,8 +22,10 @@ import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import org.junit.Test;
|
||||
@@ -325,6 +327,25 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
|
||||
|
||||
assertEquals(3.4d, expression.getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void repeatedCompilation() throws Exception {
|
||||
// Verifying that after a number of compilations, the classloaders
|
||||
// used to load the compiled expressions are discarded/replaced.
|
||||
// See SpelCompiler.loadClass()
|
||||
Field f = SpelExpression.class.getDeclaredField("compiledAst");
|
||||
Set<Object> classloadersUsed = new HashSet<>();
|
||||
for (int i=0;i<1500;i++) { // 1500 is greater than SpelCompiler.CLASSES_DEFINED_LIMIT
|
||||
expression = parser.parseExpression("4 + 5");
|
||||
assertEquals(9, (int)expression.getValue(Integer.class));
|
||||
assertCanCompile(expression);
|
||||
f.setAccessible(true);
|
||||
CompiledExpression cEx = (CompiledExpression)f.get(expression);
|
||||
classloadersUsed.add(cEx.getClass().getClassLoader());
|
||||
assertEquals(9, (int)expression.getValue(Integer.class));
|
||||
}
|
||||
assertTrue(classloadersUsed.size() > 1);
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user