[英]Java not garbage collecting memory
我正在讀取一個非常大的文件,並從每一行中提取一小部分文本。 但是在操作結束時,我的內存很少。 在讀取文件后,垃圾收集器似乎無法釋放內存。
我的問題是:有沒有辦法釋放這段記憶? 或者這是一個JVM錯誤?
我創建了一個SSCCE來證明這一點。 它讀取1 mb(由於16位編碼而在Java中為2 mb)文件並從每行中提取一個字符(~4000行,因此應該是大約8 kb)。 在測試結束時,仍然使用了完整的2 mb!
最初的內存使用情況:
Allocated: 93847.55 kb
Free: 93357.23 kb
讀取文件后立即(在任何手動垃圾收集之前):
Allocated: 93847.55 kb
Free: 77613.45 kb (~16mb used)
這是預料之中的,因為程序正在使用大量資源來讀取文件。
然而,我垃圾收集,但不是所有的內存都被釋放:
Allocated: 93847.55 kb
Free: 91214.78 kb (~2 mb used! That's the entire file!)
我知道手動調用垃圾收集器不會給你任何保證(在某些情況下它是懶惰的)。 然而,這發生在我的大型應用程序中,其中文件幾乎占用了所有可用內存,並且導致程序的其余部分盡管需要它而耗盡內存。 這個例子證實了我懷疑從文件中讀取的多余數據沒有被釋放。
以下是生成測試的SSCCE:
import java.io.*;
import java.util.*;
public class Test {
public static void main(String[] args) throws Throwable {
Runtime rt = Runtime.getRuntime();
double alloc = rt.totalMemory()/1000.0;
double free = rt.freeMemory()/1000.0;
System.out.printf("Allocated: %.2f kb\nFree: %.2f kb\n\n",alloc,free);
Scanner in = new Scanner(new File("my_file.txt"));
ArrayList<String> al = new ArrayList<String>();
while(in.hasNextLine()) {
String s = in.nextLine();
al.add(s.substring(0,1)); // extracts first 1 character
}
alloc = rt.totalMemory()/1000.0;
free = rt.freeMemory()/1000.0;
System.out.printf("Allocated: %.2f kb\nFree: %.2f kb\n\n",alloc,free);
in.close();
System.gc();
alloc = rt.totalMemory()/1000.0;
free = rt.freeMemory()/1000.0;
System.out.printf("Allocated: %.2f kb\nFree: %.2f kb\n\n",alloc,free);
}
}
在創建子字符串時, 您的子字符串會保留對原始字符串的char數組的引用 (此優化可以非常快速地處理字符串的許多子字符串)。 所以,當你保持你的子串的al
名單,你讓你的整個文件在內存中。 要避免這種情況,請使用以字符串作為參數的構造函數創建一個新String。
所以基本上我建議你這樣做
while(in.hasNextLine()) {
String s = in.nextLine();
al.add(new String(s.substring(0,1))); // extracts first 1 character
}
String(String)構造函數的源代碼明確指出它的用法是修剪“行李”:
164 public String(String original) {
165 int size = original.count;
166 char[] originalValue = original.value;
167 char[] v;
168 if (originalValue.length > size) {
169 // The array representing the String is bigger than the new
170 // String itself. Perhaps this constructor is being called
171 // in order to trim the baggage, so make a copy of the array.
172 int off = original.offset;
173 v = Arrays.copyOfRange(originalValue, off, off+size);
174 } else {
175 // The array representing the String is the same
176 // size as the String, so no point in making a copy.
177 v = originalValue;
178 }
179 this.offset = 0;
180 this.count = size;
181 this.value = v;
更新:這個問題在OpenJDK 7,Update 6中消失了。擁有更新版本的人沒有問題。
確保不再保留您不再需要的參考文獻。
你仍然有al
和in
引用。
嘗試添加al = null; in = null;
al = null; in = null;
在調用垃圾收集器之前。
此外,您需要了解
substring
的實現方式。
substring
保留原始字符串,並且只對同一個char[]
數組使用不同的偏移量和長度。
al.add(new String(s.substring(0,1)));
不確定是否有更優雅的方式復制子字符串。
也許
s.getChars()
對你也更有用。
從Java 8中,子確實現在復制的字符。 您可以驗證構造函數是否調用Arrays.copyOfRange
。
System.gc()不保證JVM會進行垃圾收集 - 它只是對JVM的建議,它可以嘗試並進行垃圾收集。 由於已經有很多內存可用,JVM可能會忽略建議並繼續運行直到感覺需要這樣做。
閱讀更多文檔http://docs.oracle.com/javase/6/docs/api/java/lang/System.html#gc()
關於它的另一個問題可以在什么時候使用System.gc()做什么
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.