[英]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.");
}
}
如何再次减少内存使用量?
for (int i = 0; i < 10; i++) { ArchiveLoader archiveLoader = new ArchiveLoader(FileManager.getClientLocation()); archiveLoader.load(); archiveLoader.clear(); }
当我运行此命令时,内存使用率一直上升到660mb,然后下降到526mb。 从那时起,它将不再停止下降。
为什么首先要清除地图? 仅当您打算将归档加载程序实例重新用于另一个归档时,才有意义。 如果不想这样做,为什么不立即实例化另一个实例,一旦不再引用它,让GC自动清除它呢? 似乎您正在尝试执行过多的内存管理,而不是用Java进行编码时所需的。 我不认为您一开始就存在内存泄漏,就像鲍里斯(Boris)所说的那样,内存使用率不会立即下降。 如果一遍又一遍地加载档案后,实际上内存不足,那么您就会知道内存泄漏。 否则,分析就不是那么简单。
问:是否存在内存泄漏?
我完全不相信您有内存泄漏。 实际上,如果可访问的数据量高度可变,这看起来将使我期望非泄漏程序的行为。
首先,您正在查看操作系统报告的内存使用情况。 这包括JVM使用的所有内存,包括各种堆外资源,例如本机库和堆栈。 它还包括堆开销,例如撤离空间; 例如,内存计入堆的“可用空间”。
为了确定您是否有实际的内存泄漏(在Java堆中),您需要查看一段时间内的最小和最大堆使用情况。 具体来说,您需要在多个GC周期内运行GC之前和之后保持“已使用”和“免费”值。 如果这些值(在这些时间点)随着时间呈明显的上升趋势,那么您就有问题了。
问:您如何掌握这些信息?
简单的方法是使用此处所述的Oracle visualvm工具。 内存使用情况图看起来像是锯齿形的,具有与垃圾回收相对应的“峰值”和“谷”。 您正在寻找的是峰谷高度的长期上升趋势。
如果visualvm提供了(真实的)泄漏证据,那么它也提供了帮助您进行跟踪的工具。
问:那为什么Windows会说您使用了那么多内存?
好吧,基本上,您正在使用该内存。 JVM要求操作系统提供足够的内存,以使堆达到容纳所有对象所需的大小。 在您的情况下,“需求”在两个极端之间波动。 JVM的问题如下:
它不知道您的应用程序将要做什么。 它不知道您的应用程序需要多少内存,何时释放它以及是否要返回它。
它仅在运行GC时“执行某些操作”,并且仅在“空间”变满或接近充满时才会发生。 (这不太可能与您的clear()
调用相对应。)
JVM将未使用的内存返还给OS的成本很高。 需要移动对象,以便可以调整“空间”的大小而不会分散地址空间。
因此,这意味着如果您的应用程序具有“突发”的内存需求配置文件,则JVM可能会调整堆大小以容纳最大需求,并从长远来看将其保持在该级别。
这并不是说JVM从不回馈内存。 根据JVM堆调整参数的不同,如果JVM在多个GC周期后发现堆太大,则会通过返还内存来减少堆。 但是,它以保守/勉强的方式执行此操作。 不愿意的原因是:
如果堆很大,则垃圾收集效率更高。
再次增加堆是要避免的开销。
问:您应该运行
System.gc()
吗?
没有! 没有! 没有!
可以强制GC运行(如上所述),但是将系统的性能留给JVM来决定何时对它有效是更好的系统性能。
此外,如果您的目标是减少系统级别的内存使用量,则不能保证运行GC会导致JVM将任何内存返还给OS。
问:如何使用尽可能少的(内存)资源。
用非托管语言(如C或C ++)重写应用程序,并实现自己的内存管理。
不要在内存中缓存JAR文件内容。
我希望其他答案可以保证内存泄漏和JVM行为。
仍然您的程序可能会泄漏-例外情况(但是在日志中显示)。 可是一件小事。
使用try-with-resources可以防止异常等资源泄漏。
try (FileInputStream fis = new FileInputStream(archive)) {
loadStream(fis);
} // Always closes fis.
但是,在您的情况下,代码将FileInputStream包装在ZipInputStream中,并且代码关闭了3次,而通常情况下,仅关闭ZipInputStream。
使用this.archive
使用loadStream()
进行重新设计似乎最好,使用try-with-resources关闭ZipInputStream。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.