<meta http-equiv="Content-Type" content="text/html; charset=GB18030"><b><span style="color: rgb(51, 51, 51); font-family: "Helvetica Neue", "Segoe UI", Roboto, Helvetica, Arial, sans-serif;">System / OS / Java Runtime Information</span></b><div><span style="color: rgb(51, 51, 51); font-family: "Helvetica Neue", "Segoe UI", Roboto, Helvetica, Arial, sans-serif;">Windows 11 23H2</span></div><div><span style="color: rgb(51, 51, 51); font-family: "Helvetica Neue", "Segoe UI", Roboto, Helvetica, Arial, sans-serif;">openjdk 22.0.1 2024-04-16</span></div><div><span style="color: rgb(51, 51, 51); font-family: "Helvetica Neue", "Segoe UI", Roboto, Helvetica, Arial, sans-serif;">OpenJDK Runtime Environment (build 22.0.1+8-16)</span></div><div><span style="color: rgb(51, 51, 51); font-family: "Helvetica Neue", "Segoe UI", Roboto, Helvetica, Arial, sans-serif;">OpenJDK 64-Bit Server VM (build 22.0.1+8-16, mixed mode, sharing)</span><div style=""><font color="#333333" face="Helvetica Neue, Segoe UI, Roboto, Helvetica, Arial, sans-serif"><br></font></div><div style=""><b><span style="color: rgb(51, 51, 51); font-family: "Helvetica Neue", "Segoe UI", Roboto, Helvetica, Arial, sans-serif;">Description</span></b></div><div style=""><span style="color: rgb(51, 51, 51); font-family: "Helvetica Neue", "Segoe UI", Roboto, Helvetica, Arial, sans-serif;">com.sun.tools.javac.code.Scope.ScopeListenerList#listeners is not cleaned up in time, which leads to the accumulation of List and WeakReference when reusing Context, leading to OOM.</span></div><div style=""><font class="notranslate immersive-translate-target-wrapper" lang="zh-CN" data-immersive-translate-translation-element-mark="1" style="box-sizing: border-box; color: rgb(51, 51, 51); font-family: "Helvetica Neue", "Segoe UI", Roboto, Helvetica, Arial, sans-serif;"><b><br></b></font></div><div style=""><span style="color: rgb(51, 51, 51); font-family: "Helvetica Neue", "Segoe UI", Roboto, Helvetica, Arial, sans-serif;"><b>Reproduce</b></span><font class="notranslate immersive-translate-target-wrapper" lang="zh-CN" data-immersive-translate-translation-element-mark="1" style="box-sizing: border-box; color: rgb(51, 51, 51); font-family: "Helvetica Neue", "Segoe UI", Roboto, Helvetica, Arial, sans-serif;"></font></div><div style=""><font class="notranslate immersive-translate-target-wrapper" lang="zh-CN" data-immersive-translate-translation-element-mark="1" style="box-sizing: border-box;" color="#333333" face="Helvetica Neue, Segoe UI, Roboto, Helvetica, Arial, sans-serif">Using JavacTaskPool to compile the same source code repeatedly, the memory usage rises steadily and cannot be recycled until OOM .</font></div><div style=""><font class="notranslate immersive-translate-target-wrapper" lang="zh-CN" data-immersive-translate-translation-element-mark="1" style="box-sizing: border-box;" color="#333333" face="Helvetica Neue, Segoe UI, Roboto, Helvetica, Arial, sans-serif"><br></font></div><div style=""><span style="color: rgb(51, 51, 51); font-family: "Helvetica Neue", "Segoe UI", Roboto, Helvetica, Arial, sans-serif;"><b>Expected Result</b></span></div><div style=""><font color="#333333" face="Helvetica Neue, Segoe UI, Roboto, Helvetica, Arial, sans-serif">The heap memory size is stable and runs continuously.</font></div><div style=""><font color="#333333" face="Helvetica Neue, Segoe UI, Roboto, Helvetica, Arial, sans-serif"><br></font></div><div style=""><span style="color: rgb(51, 51, 51); font-family: "Helvetica Neue", "Segoe UI", Roboto, Helvetica, Arial, sans-serif;"><b>Actual Result</b></span><font class="notranslate immersive-translate-target-wrapper" lang="zh-CN" data-immersive-translate-translation-element-mark="1" style="box-sizing: border-box; color: rgb(51, 51, 51); font-family: "Helvetica Neue", "Segoe UI", Roboto, Helvetica, Arial, sans-serif;"><b> </b></font></div><div style=""><font class="notranslate immersive-translate-target-wrapper" lang="zh-CN" data-immersive-translate-translation-element-mark="1" style="box-sizing: border-box;"><div style="color: rgb(51, 51, 51); font-family: "Helvetica Neue", "Segoe UI", Roboto, Helvetica, Arial, sans-serif;">Exception in thread "main" java.lang.OutOfMemoryError: Java heap space</div><div style="color: rgb(51, 51, 51); font-family: "Helvetica Neue", "Segoe UI", Roboto, Helvetica, Arial, sans-serif;"> at java.base/java.util.List.of(List.java:1030)</div><div style="color: rgb(51, 51, 51); font-family: "Helvetica Neue", "Segoe UI", Roboto, Helvetica, Arial, sans-serif;"> at ReproduceTest.main(ReproduceTest.java:35)</div><div style="color: rgb(51, 51, 51); font-family: "Helvetica Neue", "Segoe UI", Roboto, Helvetica, Arial, sans-serif;"><br></div><div style="color: rgb(51, 51, 51); font-family: "Helvetica Neue", "Segoe UI", Roboto, Helvetica, Arial, sans-serif;"><b>Source code for an executable test case</b></div><div style=""><pre style=""><font color="#333333" face="Helvetica Neue, Segoe UI, Roboto, Helvetica, Arial, sans-serif" size="1">import com.sun.tools.javac.api.JavacTaskPool;
import javax.tools.*;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;
public class ReproduceTest {
private static final String source = """
import java.util.function.BiFunction;
public class LambdaContainer {
public static BiFunction<Integer, Integer, Integer> getLambda() {
return (x, y) -> x + y;
}
}
""";
public static void main(String[] args) throws URISyntaxException {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
JavacTaskPool javacTaskPool = new JavacTaskPool(1);
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, null, null);
MemoryFileManager memoryFileManager = new MemoryFileManager(fileManager);
List<?> list;
do {
System.gc();
list = javacTaskPool.getTask(null, memoryFileManager, diagnostics,
List.of("-source", "22", "-target", "22", "-encoding", "UTF-8"), null
, List.of(new MemoryInputJavaFileObject("LambdaContainer.java", source)), task -> {
if (task.call()) {
try (memoryFileManager) {
return memoryFileManager.getOutputs();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return Collections.emptyList();
});
} while (!list.isEmpty());
}
static class MemoryFileManager extends ForwardingJavaFileManager<JavaFileManager> {
private final List<MemoryOutputJavaFileObject> outputs = new ArrayList<>();
// Simulate the cache implementation to speed up the run,
// repeated runs of the same class compilation will not cause the cache memory usage to keep growing
private final Map<String, String> binaryNameCache = new HashMap<>();
private final Map<String, Iterable<JavaFileObject>> fileListCache = new HashMap<>();
protected MemoryFileManager(JavaFileManager fileManager) {
super(fileManager);
}
@Override
public String inferBinaryName(Location location, JavaFileObject file) {
if (file instanceof BinaryJavaFileObject b) {
String binaryName = b.getBinaryName();
if (binaryName != null) {
return binaryName;
}
}
return binaryNameCache.computeIfAbsent(location.getName() + file.toString(), k -> super.inferBinaryName(location, file));
}
@Override
public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException {
if (kind == JavaFileObject.Kind.CLASS) {
var fileObject = new MemoryOutputJavaFileObject(className);
outputs.add(fileObject);
return fileObject;
}
return super.getJavaFileForOutput(location, className, kind, sibling);
}
@Override
public Iterable<JavaFileObject> list(Location location, String packageName, Set<JavaFileObject.Kind> kinds, boolean recurse) throws IOException {
String key = location.getName() + ":" + packageName + ":" + kinds + ":" + recurse;
return fileListCache.computeIfAbsent(key, k -> {
try {
return super.list(location, packageName, kinds, recurse);
} catch (IOException e) {
return Collections.emptyList();
}
});
}
public List<MemoryOutputJavaFileObject> getOutputs() {
return new ArrayList<>(outputs);
}
@Override
public void close() throws IOException {
super.close();
outputs.clear();
}
}
interface BinaryJavaFileObject extends JavaFileObject {
String getBinaryName();
}
static class MemoryInputJavaFileObject extends SimpleJavaFileObject {
private final String content;
public MemoryInputJavaFileObject(String uri, String content) throws URISyntaxException {
super(new URI("string:///" + uri), Kind.SOURCE);
this.content = content;
}
@Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
return content;
}
}
static class MemoryOutputJavaFileObject extends SimpleJavaFileObject implements BinaryJavaFileObject {
private final ByteArrayOutputStream stream;
private final String binaryName;
public MemoryOutputJavaFileObject(String name) {
super(URI.create("string:///" + name.replace('.', '/') + Kind.CLASS.extension), Kind.CLASS);
this.binaryName = name;
this.stream = new ByteArrayOutputStream();
}
public byte[] toByteArray() {
return stream.toByteArray();
}
public String getBinaryName() {
return binaryName;
}
@Override
public InputStream openInputStream() {
return new ByteArrayInputStream(toByteArray());
}
@Override
public ByteArrayOutputStream openOutputStream() {
return this.stream;
}
}
}</font><span style="color: rgb(51, 51, 51); font-family: "Helvetica Neue", "Segoe UI", Roboto, Helvetica, Arial, sans-serif;">
</span></pre><div><br></div><pre style="color: rgb(51, 51, 51); font-family: "Helvetica Neue", "Segoe UI", Roboto, Helvetica, Arial, sans-serif;"><span style="font-family: "Helvetica Neue", "Segoe UI", Roboto, Helvetica, Arial, sans-serif; white-space-collapse: collapse;"><b>Workaround</b></span></pre><pre style=""><pre style=""><font color="#333333" face="Helvetica Neue, Segoe UI, Roboto, Helvetica, Arial, sans-serif"><span style="white-space-collapse: collapse;">1. Through reflection, after each call to task.call(), clean up context's Types.MembersClosureCache#nilScope And the Scope.ScopeListenerList#listeners of each ClassSymbol in com.sun.tools.javac.code.Symtab#classes.</span></font></pre><pre style=""><font color="#333333" face="Helvetica Neue, Segoe UI, Roboto, Helvetica, Arial, sans-serif"><span style="white-space-collapse: collapse;"><br></span></font></pre><pre style=""><font color="#333333" face="Helvetica Neue, Segoe UI, Roboto, Helvetica, Arial, sans-serif"><span style="white-space-collapse: collapse;">2. Use agent (</span></font>attachment MemoryLeakFixAgent.java) to change the Scope.ScopeListenerList to the following implementation:</pre><pre style=""><font color="#333333" face="Helvetica Neue, Segoe UI, Roboto, Helvetica, Arial, sans-serif" size="1"><span style="white-space-collapse: collapse;"> public static class ScopeListenerList {</span></font></pre><pre style=""><font color="#333333" face="Helvetica Neue, Segoe UI, Roboto, Helvetica, Arial, sans-serif" size="1"><span style="white-space-collapse: collapse;"> Set<ScopeListener> listeners = Collections.newSetFromMap(new WeakHashMap<>());</span></font></pre><pre style=""><font color="#333333" face="Helvetica Neue, Segoe UI, Roboto, Helvetica, Arial, sans-serif" size="1"><span style="white-space-collapse: collapse;"> void add(ScopeListener sl) {</span></font></pre><pre style=""><font color="#333333" face="Helvetica Neue, Segoe UI, Roboto, Helvetica, Arial, sans-serif" size="1"><span style="white-space-collapse: collapse;"> listeners.add(sl);</span></font></pre><pre style=""><font color="#333333" face="Helvetica Neue, Segoe UI, Roboto, Helvetica, Arial, sans-serif" size="1"><span style="white-space-collapse: collapse;"> }</span></font></pre><pre style=""><font color="#333333" face="Helvetica Neue, Segoe UI, Roboto, Helvetica, Arial, sans-serif" size="1"><span style="white-space-collapse: collapse;"> void symbolAdded(Symbol sym, Scope scope) {</span></font></pre><pre style=""><font color="#333333" face="Helvetica Neue, Segoe UI, Roboto, Helvetica, Arial, sans-serif" size="1"><span style="white-space-collapse: collapse;"> walkReferences(sym, scope, false);</span></font></pre><pre style=""><font color="#333333" face="Helvetica Neue, Segoe UI, Roboto, Helvetica, Arial, sans-serif" size="1"><span style="white-space-collapse: collapse;"> }</span></font></pre><pre style=""><font color="#333333" face="Helvetica Neue, Segoe UI, Roboto, Helvetica, Arial, sans-serif" size="1"><span style="white-space-collapse: collapse;"> void symbolRemoved(Symbol sym, Scope scope) {</span></font></pre><pre style=""><font color="#333333" face="Helvetica Neue, Segoe UI, Roboto, Helvetica, Arial, sans-serif" size="1"><span style="white-space-collapse: collapse;"> walkReferences(sym, scope, true);</span></font></pre><pre style=""><font color="#333333" face="Helvetica Neue, Segoe UI, Roboto, Helvetica, Arial, sans-serif" size="1"><span style="white-space-collapse: collapse;"> }</span></font></pre><pre style=""><font color="#333333" face="Helvetica Neue, Segoe UI, Roboto, Helvetica, Arial, sans-serif" size="1"><span style="white-space-collapse: collapse;"> private void walkReferences(Symbol sym, Scope scope, boolean isRemove) {</span></font></pre><pre style=""><font color="#333333" face="Helvetica Neue, Segoe UI, Roboto, Helvetica, Arial, sans-serif" size="1"><span style="white-space-collapse: collapse;"> for (ScopeListener sl : listeners) {</span></font></pre><pre style=""><font color="#333333" face="Helvetica Neue, Segoe UI, Roboto, Helvetica, Arial, sans-serif" size="1"><span style="white-space-collapse: collapse;"> if (isRemove) {</span></font></pre><pre style=""><font color="#333333" face="Helvetica Neue, Segoe UI, Roboto, Helvetica, Arial, sans-serif" size="1"><span style="white-space-collapse: collapse;"> sl.symbolRemoved(sym, scope);</span></font></pre><pre style=""><font color="#333333" face="Helvetica Neue, Segoe UI, Roboto, Helvetica, Arial, sans-serif" size="1"><span style="white-space-collapse: collapse;"> } else {</span></font></pre><pre style=""><font color="#333333" face="Helvetica Neue, Segoe UI, Roboto, Helvetica, Arial, sans-serif" size="1"><span style="white-space-collapse: collapse;"> sl.symbolAdded(sym, scope);</span></font></pre><pre style=""><font color="#333333" face="Helvetica Neue, Segoe UI, Roboto, Helvetica, Arial, sans-serif" size="1"><span style="white-space-collapse: collapse;"> }</span></font></pre><pre style=""><font color="#333333" face="Helvetica Neue, Segoe UI, Roboto, Helvetica, Arial, sans-serif" size="1"><span style="white-space-collapse: collapse;"> }</span></font></pre><pre style=""><font color="#333333" face="Helvetica Neue, Segoe UI, Roboto, Helvetica, Arial, sans-serif" size="1"><span style="white-space-collapse: collapse;"> }</span></font></pre><pre style=""><font color="#333333" face="Helvetica Neue, Segoe UI, Roboto, Helvetica, Arial, sans-serif" size="1"><span style="white-space-collapse: collapse;"> }</span></font></pre><pre style=""><font color="#333333" face="Helvetica Neue, Segoe UI, Roboto, Helvetica, Arial, sans-serif"><span style="white-space-collapse: collapse;"><br></span></font></pre></pre></div></font></div></div>