Сериализуемое, клонированное и использование памяти в Java
Я использую внутренний класс, который является подклассом HashMap. У меня есть String
как ключ и double[]
как значения. Я храню около 200 удвоений за double[]
. Я должен использовать около 700 МБ для хранения ключей, указателей и парных. Тем не менее, анализ памяти показывает, что мне нужно намного больше (чуть больше 2 ГБ).
Используя TIJmp (инструмент профилирования), я увидел, что был char[]
, который использовал почти половину общей памяти. TIJmp сказал, что char[]
пришел от Serializable
и Cloneable
. Значения в нем варьировались от списка шрифтов и путей по умолчанию к сообщениям и одиночным символам.
Каково точное поведение Serializable
в JVM? Сохраняет ли он постоянную копию во все времена, удваивая размер моего объема памяти? Как я могу записать двоичные копии объекта во время выполнения, не превращая JVM в память?
PS: метод, в котором потребление памяти увеличивается больше всего, ниже. Файл имеет около 229 000 строк и 202 поля на строку.
public void readThetas(String filename) throws Exception
{
long t1 = System.currentTimeMillis();
documents = new HashMapX<String,double[]>(); //Document names to indices.
Scanner s = new Scanner(new File(filename));
int docIndex = 0;
if (s.hasNextLine())
System.out.println(s.nextLine()); // Consume useless first line :)
while(s.hasNextLine())
{
String[] fields = s.nextLine().split("\\s+");
String docName = fields[1];
numTopics = fields.length/2-1;
double[] thetas = new double[numTopics];
for (int i=2;i<numTopics;i=i+2)
thetas[Integer.valueOf(fields[i].trim())] = Double.valueOf(fields[i+1].trim());
documents.put(docName,thetas);
docIndex++;
if (docIndex%10000==0)
System.out.print("*"); //progress bar ;)
}
s.close();
long t2 = System.currentTimeMillis();
System.out.println("\nRead file in "+ (t2-t1) +" ms");
}
Oh!, а HashMapX - это внутренний класс, объявленный следующим образом:
public static class HashMapX< K, V> extends HashMap<K,V> {
public V get(Object key, V altVal) {
if (this.containsKey(key))
return this.get(key);
else
return altVal;
}
}
Ответы
Ответ 1
Итак, я нашел ответ. Это утечка памяти в моем коде. Не имел никакого отношения к Serializable или Cloneable.
Этот код пытается проанализировать файл. Каждая строка содержит набор значений, которые я пытаюсь извлечь. Затем я сохраняю некоторые из этих значений и сохраняю их в HashMapX или какой-либо другой структуре.
Ядро проблемы находится здесь:
String[] fields = s.nextLine().split("\\s+");
String docName = fields[1];
и я его распространяю здесь:
documents.put(docName,thetas);
Что происходит, так это то, что docName является ссылкой на элемент в массиве (полях), и я сохраняю эту ссылку для жизни программы (сохраняя ее в глобальных документах HashMap). Пока я сохраняю эту ссылку в живых, все поля String [] не могут быть собраны в мусор. Решение:
String docName = new String(fields[1]); // A copy, not a reference.
Таким образом, копирование объекта и освобождение ссылки на элемент массива. Таким образом сборщик мусора может освободить память, используемую массивом, после обработки каждого поля.
Я надеюсь, что это будет полезно для всех тех, кто разбирает большие текстовые файлы с помощью split и сохраняет некоторые из полей глобальных переменных.
Спасибо всем за их комментарии. Они направили меня в правильном направлении.
Ответ 2
Это может не затрагивать все ваши вопросы, но это способ, с помощью которого сериализация может значительно увеличить использование памяти: http://java.sun.com/javase/technologies/core/basic/serializationFAQ.jsp#OutOfMemoryError.
Короче говоря, если вы держите открытое ObjectOutputStream
, то ни один из объектов, которые были записаны на него, не может быть собран в мусор, если вы явно не назовете его метод reset()
.