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。从那时起,它就不会再停止下降了。
答案 0 :(得分:0)
为什么要首先清除地图?如果您打算将存档加载器实例重新用于其他存档,那只会有意义。为什么不只是实例化另一个实例,如果你想这样做,并且一旦你没有引用它就让GC自动清除它?在使用Java进行编码时,您似乎正在尝试进行过多的内存管理。我不认为你首先有内存泄漏,就像鲍里斯所说,内存使用率不会立即下降。如果在反复加载存档后实际上内存不足,那么您就会知道存在内存泄漏。否则,分析就不那么简单了。
答案 1 :(得分:0)
问:是否有内存泄漏?
我不相信你有内存泄漏。事实上,如果可达数据的数量变化很大,我认为如果不泄漏的程序就会出现这种情况。
首先,您正在查看操作系统报告的内存使用情况。这包括JVM使用的所有内存,包括各种堆外资源,如本机库和堆栈。它还包括堆空间,例如疏散空间;例如内存计入堆的“自由空间”。
要确定您是否有真正的内存泄漏(在Java堆中),您需要查看一段时间内的最小和最大堆使用情况。具体来说,您需要在运行GC之前和之后获取“已使用”和“自由”值....在多个GC循环中。如果这些值(在这些点)随着时间的推移有明显的上升趋势,那么你就会遇到问题。
问:你如何掌握这些信息?
简单的方法是使用此处所述的Oracle visualvm工具。内存使用情况图看起来像一个锯齿,其中“peak”和“valley”对应于垃圾收集。您正在寻找的是高峰和低谷的长期向上趋势。
如果visualvm提供(真实)泄漏证据,那么它还有工具可以帮助您追踪它们。
问:那么为什么Windows说你使用了这么多内存?
好吧,基本上你正在使用那个记忆。 JVM要求操作系统提供足够的内存,以使堆尽可能大,以容纳所有对象。在您的情况下,“需求”在两个极端之间波动。 JVM的问题如下:
它不知道您的应用程序将要执行的操作。它不知道你的应用程序要求多少内存,什么时候发布它以及它是否会要求它回来。
它只在运行GC时“做某事”,而且只有当“空间”变满或接近满时才会发生。 (这不太可能与您的clear()
来电相对应。)
JVM将未使用的内存返回给操作系统会产生很大的成本。需要移动对象,以便可以调整“空格”的大小而不会破坏地址空间。
所以这意味着如果你有一个具有“突发”内存需求配置文件的应用程序,那么JVM很可能会调整堆的大小以保持最大需求,并且长期保持在该级别。
这并不是说JVM永远不会回忆内存。根据JVM堆调整参数,如果JVM在多个GC周期后发现堆太大,它将通过返回内存来减少堆。然而,它以保守/不情愿的方式做到这一点。不情愿的原因是:
如果堆很大,垃圾收集效率会更高。
增加堆(再次)是一个它想要避免的开销。
问:你应该运行
System.gc()
吗?
没有!没有!不!
可以强制GC运行(如上所述),但是将系统性能留给JVM以确定它何时高效来更好。
此外,您无法保证运行GC会导致JVM将任何内存返回给操作系统...如果您的目标是减少系统级别的内存使用量。
问:我如何使用尽可能少的(内存)资源。
重写您的应用程序是一种非托管语言,如C或C ++,并实现您自己的内存管理。
不要将JAR文件内容缓存在内存中。
答案 2 :(得分:0)
我希望其他答案提供了保证w.r.t.内存泄漏和JVM行为。
您的程序仍可能泄漏 - 例外情况(在日志中显示)。然而,这是一件小事。
使用try-with-resources防止异常等资源泄漏。
try (FileInputStream fis = new FileInputStream(archive)) {
loadStream(fis);
} // Always closes fis.
但是,在您的情况下,代码将ZipInputStream中的FileInputStream包装起来,代码关闭三次,通常只关闭ZipInputStream。
重新设计loadStream()
使用this.archive
似乎最好,使用try-with-resources关闭ZipInputStream。