我该如何修复此内存泄漏?
How can I fix this memory leak?
import java.io.*;
import java.util.HashMap;
import java.util.logging.Logger;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
public class ArchiveLoader {
private static final Logger logger = Logger.getLogger(Landing.class.getName());
private final String PREFIX = ".class";
private final byte[] BUFFER = new byte[1024];
private File archive;
private HashMap<String, byte[]> classMap = new HashMap<>();
public ArchiveLoader(String archivePath) throws IOException {
this.archive = new File(archivePath);
}
public void load() throws IOException {
FileInputStream fis = new FileInputStream(archive);
loadStream(fis);
fis.close();
}
private void loadStream(InputStream inputStream) throws IOException {
if (archive.canRead()) {
if (classMap.size() == 0) {
ZipInputStream zis = new ZipInputStream(inputStream);
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
String name = entry.getName();
if (name.toLowerCase().endsWith(PREFIX)) {
name = name.substring(0, name.indexOf(PREFIX));
ByteArrayOutputStream bos = new ByteArrayOutputStream();
int read;
while ((read = zis.read(BUFFER, 0, BUFFER.length)) != -1) {
bos.write(BUFFER, 0, read);
}
zis.closeEntry();
bos.close();
classMap.put(name, bos.toByteArray());
}
}
inputStream.close();
zis.close();
logger.info("Loaded " + classMap.size() + " classes.");
} else {
logger.info("Archive has already been loaded!");
}
} else {
throw new IOException("Could not read the JAR archive.");
}
}
public void clear() {
classMap.clear();
System.out.println(classMap.size());
classMap = null;
logger.info("`enter code here`Cleared the ArchiveLoader.");
}
}
- 在加载我的 JAR 存档之前,内存使用量约为 14mb。
- 当我用 class 加载一个 jar 文件时,内存使用量变为
大约 210mb.
- 当我调用 clear 时,内存使用量没有减少。
如何再次降低内存使用量?
for (int i = 0; i < 10; i++) {
ArchiveLoader archiveLoader = new ArchiveLoader(FileManager.getClientLocation());
archiveLoader.load();
archiveLoader.clear();
}
当我运行这个时,内存使用量一路上升到660mb
然后减少到 526mb。从那时起,它就不会再停止下降了。
你为什么要首先清除地图?如果您打算将存档加载器实例重新用于另一个存档,那才有意义。如果你想这样做,为什么不直接实例化另一个实例,一旦你不再引用它,让 GC 自动清除这个实例?在 Java 中编码时,您似乎尝试进行过多的内存管理。我不认为你首先有内存泄漏,就像鲍里斯说的那样,内存使用量不会立即下降。如果你在一遍又一遍地加载档案后实际上 运行 内存不足,那么你就会知道你有内存泄漏。不然分析没这么简单
Q: Is there a memory leak?
我完全不相信你有内存泄漏。事实上,如果可访问的数据量变化很大,这看起来是我期望非泄漏程序的行为方式。
首先,您正在查看 OS 报告的内存使用情况。这包括 JVM 使用的所有内存,包括各种堆外资源,例如本机库和堆栈。它还包括堆开销,例如 evacuated spaces;例如计入堆 "free space".
的内存
要确定是否存在真正的内存泄漏(在 Java 堆中),您需要查看一段时间内的最小和最大堆使用量。具体来说,您需要在 GC 之前和之后获取 "used" 和 "free" 值 运行 .... 在多个 GC 周期中。如果随着时间的推移这些值(在那些点)有明显的上升趋势,那么你就有问题了。
Q: How do you get hold of that information?
简单的方法是使用此处描述的 Oracle visualvm 工具。内存使用图看起来像锯齿波,其中 "peaks" 和 "valleys" 对应于垃圾回收。你要找的是峰谷高度的长期上升趋势。
如果 visualvm 提供了(真实的)泄漏证据,那么它还有工具可以帮助您追踪它们。
Q: So why does Windows say you are using so much memory?
好吧,基本上你正在使用那个内存。 JVM 向 OS 请求足够的内存以使堆尽可能大以容纳所有对象。在您的情况下,"demand" 在两个极端之间波动。 JVM 的问题如下:
它不知道你的应用程序要做什么。它不知道您的应用程序要请求多少内存,何时释放它以及是否要请求它。
它仅在 运行 成为 GC 时才 "does something",并且只有当 "space" 已满或接近满时才会发生这种情况。 (这不太可能对应于您的 clear()
调用。)
JVM 将未使用的内存返还给 OS 的成本很高。需要四处移动对象,以便可以调整 "spaces" 的大小而不会碎片化地址 space.
所以这意味着如果您的应用程序具有 "bursty" 内存需求配置文件,JVM 可能会调整堆的大小以容纳最大需求,并在很长一段时间内将其保持在该级别任期。
这并不是说 JVM 从不归还内存。根据 JVM 堆调整参数,如果 JVM 在多次 GC 周期后发现堆太大,它会通过返还内存来减少堆。但是,它以保守/不情愿的方式这样做。不愿意的原因是:
如果堆很大,垃圾收集效率更高。
(再次)增加堆是它想要避免的开销。
Q: Should you run System.gc()
?
没有!不!没有!
可以强制GC到运行(如上),但是对于系统性能来说,让JVM决定什么时候高效 到它。
此外,您无法保证 运行 GC 会导致 JVM 将任何内存返回给 OS ... 如果您的目标是减少内存使用系统级。
Q: How do I use as little (memory) resources as possible.
使用 C 或 C++ 等非托管语言重写您的应用程序,并实现您自己的内存管理。
不要像那样在内存中缓存 JAR 文件内容。
我希望其他答案能提供保证w.r.t。内存泄漏和 JVM 行为。
您的程序仍然可能会泄漏 - 出现异常(但在日志中显示)。然而,这是一件小事。
使用 try-with-resources 来防止异常等资源泄漏。
try (FileInputStream fis = new FileInputStream(archive)) {
loadStream(fis);
} // Always closes fis.
但是,在您的情况下,代码将 FileInputStream 包装在 ZipInputStream 中,并且代码关闭了三次,而通常一次只会关闭 ZipInputStream。
重新设计,loadStream()
使用 this.archive
似乎最好,使用 try-with-resources 关闭 ZipInputStream。
import java.io.*;
import java.util.HashMap;
import java.util.logging.Logger;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
public class ArchiveLoader {
private static final Logger logger = Logger.getLogger(Landing.class.getName());
private final String PREFIX = ".class";
private final byte[] BUFFER = new byte[1024];
private File archive;
private HashMap<String, byte[]> classMap = new HashMap<>();
public ArchiveLoader(String archivePath) throws IOException {
this.archive = new File(archivePath);
}
public void load() throws IOException {
FileInputStream fis = new FileInputStream(archive);
loadStream(fis);
fis.close();
}
private void loadStream(InputStream inputStream) throws IOException {
if (archive.canRead()) {
if (classMap.size() == 0) {
ZipInputStream zis = new ZipInputStream(inputStream);
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
String name = entry.getName();
if (name.toLowerCase().endsWith(PREFIX)) {
name = name.substring(0, name.indexOf(PREFIX));
ByteArrayOutputStream bos = new ByteArrayOutputStream();
int read;
while ((read = zis.read(BUFFER, 0, BUFFER.length)) != -1) {
bos.write(BUFFER, 0, read);
}
zis.closeEntry();
bos.close();
classMap.put(name, bos.toByteArray());
}
}
inputStream.close();
zis.close();
logger.info("Loaded " + classMap.size() + " classes.");
} else {
logger.info("Archive has already been loaded!");
}
} else {
throw new IOException("Could not read the JAR archive.");
}
}
public void clear() {
classMap.clear();
System.out.println(classMap.size());
classMap = null;
logger.info("`enter code here`Cleared the ArchiveLoader.");
}
}
- 在加载我的 JAR 存档之前,内存使用量约为 14mb。
- 当我用 class 加载一个 jar 文件时,内存使用量变为 大约 210mb.
- 当我调用 clear 时,内存使用量没有减少。
如何再次降低内存使用量?
for (int i = 0; i < 10; i++) { ArchiveLoader archiveLoader = new ArchiveLoader(FileManager.getClientLocation()); archiveLoader.load(); archiveLoader.clear(); }
当我运行这个时,内存使用量一路上升到660mb 然后减少到 526mb。从那时起,它就不会再停止下降了。
你为什么要首先清除地图?如果您打算将存档加载器实例重新用于另一个存档,那才有意义。如果你想这样做,为什么不直接实例化另一个实例,一旦你不再引用它,让 GC 自动清除这个实例?在 Java 中编码时,您似乎尝试进行过多的内存管理。我不认为你首先有内存泄漏,就像鲍里斯说的那样,内存使用量不会立即下降。如果你在一遍又一遍地加载档案后实际上 运行 内存不足,那么你就会知道你有内存泄漏。不然分析没这么简单
Q: Is there a memory leak?
我完全不相信你有内存泄漏。事实上,如果可访问的数据量变化很大,这看起来是我期望非泄漏程序的行为方式。
首先,您正在查看 OS 报告的内存使用情况。这包括 JVM 使用的所有内存,包括各种堆外资源,例如本机库和堆栈。它还包括堆开销,例如 evacuated spaces;例如计入堆 "free space".
的内存要确定是否存在真正的内存泄漏(在 Java 堆中),您需要查看一段时间内的最小和最大堆使用量。具体来说,您需要在 GC 之前和之后获取 "used" 和 "free" 值 运行 .... 在多个 GC 周期中。如果随着时间的推移这些值(在那些点)有明显的上升趋势,那么你就有问题了。
Q: How do you get hold of that information?
简单的方法是使用此处描述的 Oracle visualvm 工具。内存使用图看起来像锯齿波,其中 "peaks" 和 "valleys" 对应于垃圾回收。你要找的是峰谷高度的长期上升趋势。
如果 visualvm 提供了(真实的)泄漏证据,那么它还有工具可以帮助您追踪它们。
Q: So why does Windows say you are using so much memory?
好吧,基本上你正在使用那个内存。 JVM 向 OS 请求足够的内存以使堆尽可能大以容纳所有对象。在您的情况下,"demand" 在两个极端之间波动。 JVM 的问题如下:
它不知道你的应用程序要做什么。它不知道您的应用程序要请求多少内存,何时释放它以及是否要请求它。
它仅在 运行 成为 GC 时才 "does something",并且只有当 "space" 已满或接近满时才会发生这种情况。 (这不太可能对应于您的
clear()
调用。)JVM 将未使用的内存返还给 OS 的成本很高。需要四处移动对象,以便可以调整 "spaces" 的大小而不会碎片化地址 space.
所以这意味着如果您的应用程序具有 "bursty" 内存需求配置文件,JVM 可能会调整堆的大小以容纳最大需求,并在很长一段时间内将其保持在该级别任期。
这并不是说 JVM 从不归还内存。根据 JVM 堆调整参数,如果 JVM 在多次 GC 周期后发现堆太大,它会通过返还内存来减少堆。但是,它以保守/不情愿的方式这样做。不愿意的原因是:
如果堆很大,垃圾收集效率更高。
(再次)增加堆是它想要避免的开销。
Q: Should you run
System.gc()
?
没有!不!没有!
可以强制GC到运行(如上),但是对于系统性能来说,让JVM决定什么时候高效 到它。
此外,您无法保证 运行 GC 会导致 JVM 将任何内存返回给 OS ... 如果您的目标是减少内存使用系统级。
Q: How do I use as little (memory) resources as possible.
使用 C 或 C++ 等非托管语言重写您的应用程序,并实现您自己的内存管理。
不要像那样在内存中缓存 JAR 文件内容。
我希望其他答案能提供保证w.r.t。内存泄漏和 JVM 行为。
您的程序仍然可能会泄漏 - 出现异常(但在日志中显示)。然而,这是一件小事。
使用 try-with-resources 来防止异常等资源泄漏。
try (FileInputStream fis = new FileInputStream(archive)) {
loadStream(fis);
} // Always closes fis.
但是,在您的情况下,代码将 FileInputStream 包装在 ZipInputStream 中,并且代码关闭了三次,而通常一次只会关闭 ZipInputStream。
重新设计,loadStream()
使用 this.archive
似乎最好,使用 try-with-resources 关闭 ZipInputStream。