[英]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.