Mar*_*tin 6 java jvm memory-leaks
我正在尝试使用如何提取文件 jre-9/lib/modules?.
该解决方案有效,但似乎分配给读取 Java 运行时映像内容的资源从未被释放,从而导致内存泄漏,例如可以通过 VisualVM 观察到:
如何修复以下再现中的内存泄漏?
package leak;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.Map;
import java.util.stream.Stream;
public class JrtfsLeak {
public static void main(String[] args) throws Exception {
Path javaHome = Paths.get(args[0]);
for (int i = 0; i < 100000; ++i) {
modules(javaHome).close();
}
}
private static Stream<Path> modules(Path javaHome) throws Exception {
Map<String, String> env = Collections.singletonMap("java.home", javaHome.toString());
Path jrtfsJar = javaHome.resolve("lib").resolve("jrt-fs.jar");
try (URLClassLoader classloader = new URLClassLoader(new URL[] { jrtfsJar.toUri().toURL() })) {
try (FileSystem fs = FileSystems.newFileSystem(URI.create("jrt:/"), env, classloader)) {
Path modulesRoot = fs.getPath("modules");
return Files.list(modulesRoot);
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
请注意,当您在 Java\xc2\xa09 或更高版本下运行时,底层实现将为jrt-fs.jar您指定java.home选项时创建一个新的类加载器。因此,这些类不是由您加载的URLClassLoader,而是由不同的类加载器加载的。当您不需要 xe2x80x99 9 之前的版本的支持时,您可以省略类加载器的创建。
无论哪种情况,它们\xe2\x80\x99都由自定义类加载器加载,并且可以在垃圾收集器支持时卸载。但该类jdk.internal.jimage.ImageBufferCache包含:
private static final ThreadLocal<BufferReference[]> CACHE =\n new ThreadLocal<BufferReference[]>() {\n @Override\n protected BufferReference[] initialValue() {\n // 1 extra slot to simplify logic of releaseBuffer()\n return new BufferReference[MAX_CACHED_BUFFERS + 1];\n }\n };\nRun Code Online (Sandbox Code Playgroud)\n如How does this ThreadLocal Prevent the Classloader from getting GCed中所述,从值到线程本地的反向引用可以阻止其垃圾回收,并且当线程本地存储在变量中时static,对同一线程加载的类之一的引用类加载器就足够了。
在这里,该值是一个 数组BufferReference,这意味着即使该数组的所有条目都已被清除,数组类型本身也具有对该文件系统的类加载器的隐式引用。
但由于它是一个线程局部变量,我们可以通过让关键线程死亡来解决它。当我将你的代码更改为
\npublic static void main(String[] args) throws InterruptedException {\n Path javaHome = Paths.get(args[0]);\n Runnable r = () -> test(javaHome);\n for(int i = 0; i < 1000; ++i) {\n Thread thread = new Thread(r);\n thread.start();\n thread.join();\n }\n}\n\nstatic void test(Path javaHome) {\n for (int i = 0; i < 1000; ++i) {\n try(var s = modules(javaHome)) {}\n catch(IOException ex) {\n throw new UncheckedIOException(ex);\n }\n catch(Exception ex) {\n throw new IllegalStateException(ex);\n }\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n类被卸载。
\n