Java не сбор памяти для сбора мусора
Я читаю очень большой файл и извлекаю некоторые небольшие части текста из каждой строки. Однако в конце операции у меня осталось очень мало памяти для работы. Похоже, что сборщик мусора не может освободить память после чтения в файле.
Мой вопрос: есть ли способ освободить эту память? Или это ошибка JVM?
Я создал SSCCE, чтобы продемонстрировать это. Он читает в файле размером 1 мб (Java из-за 16-битного кодирования) и извлекает один символ из каждой строки (~ 4000 строк, поэтому должно быть около 8 кб). В конце теста все еще используется 2 МБ!
Исходное использование памяти:
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 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);
}
}
Ответы
Ответ 1
При создании подстроки ваша подстрока сохраняет ссылку на массив char исходной строки (эта оптимизация очень быстро обрабатывает многие подстроки строки). Итак, поскольку вы сохраняете свои подстроки в списке al
, вы сохраняете весь свой файл в памяти. Чтобы этого избежать, создайте новую строку, используя конструктор, который принимает строку в качестве аргумента.
Итак, в принципе, я предлагаю вам сделать
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. Люди с более новой версией не имеют проблемы.
Ответ 2
Обязательно не оставляйте ссылки, которые вам больше не нужны.
У вас все еще есть ссылки на al
и in
.
Попробуйте добавить al = null; in = null;
перед вызовом сборщика мусора.
Кроме того, вам нужно понять, как реализуется substring
. substring
сохраняет исходную строку и просто использует другое смещение и длину для того же массива char[]
.
al.add(new String(s.substring(0,1)));
Не уверен, есть ли более элегантный способ копирования подстроки. Возможно, s.getChars()
более полезен для вас.
Как и в случае с Java 8, подстрока теперь копирует символы. Вы можете убедиться, что конструктор вызывает Arrays.copyOfRange
.
Ответ 3
System.gc() не является гарантией того, что JVM будет собирать мусор - это только совет JVM, который он может попробовать и собрать мусор. Поскольку имеется уже много памяти, JVM может игнорировать рекомендации и продолжать работать, пока не почувствует необходимость сделать это.
Подробнее о документации http://docs.oracle.com/javase/6/docs/api/java/lang/System.html#gc()
Другой вопрос, который говорит об этом, доступен в Когда System.gc() что-то делает