Сериализуемый, клонируемый и использование памяти в Java - PullRequest
14 голосов
/ 19 апреля 2011

Я использую внутренний класс, который является подклассом 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");
}

Ой! 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;
    }
}

Ответы [ 2 ]

5 голосов
/ 19 апреля 2011

Это может не ответить на все ваши вопросы, но способ, которым сериализация может значительно увеличить использование памяти: http://java.sun.com/javase/technologies/core/basic/serializationFAQ.jsp#OutOfMemoryError.

Короче говоря, если вы оставляете ObjectOutputStream открытым, то ни один из записанных в него объектов не может быть собран мусором, если вы явно не вызовете его метод reset().

4 голосов
/ 21 апреля 2011

Итак, я нашел ответ. Это утечка памяти в моем коде. Не имеет ничего общего с 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 и сохраняет некоторые поля в глобальных переменных.

Спасибо всем за комментарии. Они направили меня в правильном направлении.

...