Java из кучного пространства во время сериализации

19

Следующий код вызывает OutOfMemmoryError: пустое место для примерно 3 миллионов строк. Память, выделенная для JVM, составляет 4 ГБ, с использованием 64-битной установки.

while (rs.next())
{

    ArrayList<String> arrayList = new ArrayList<String>();
    for (int i = 1; i <= columnCount; i++)
    {
        arrayList.add(rs.getString(i));
    }

    objOS.writeObject(arrayList);
}

Память, на которую ссылается ArrayList, имеет право на сбор мусора на каждой итерации цикла while, а внутренне JVM вызывает сбор мусора ( System.gc() ), прежде чем выбросить ошибку OutOfMemory из-за кучи.

Итак, почему возникает исключение?

    
задан Nik 21.09.2011 в 07:52
источник
  • @Swaranga Sarma редактирует в то же время, не уверен, что сообщение не запуталось –  peter_budo 21.09.2011 в 07:58

2 ответа

36

Является ли objOS ObjectOutputStream ?

Если это так, то это ваша проблема: ObjectOutputStream сохраняет сильную ссылку на каждый объект, который когда-либо был написан для него, чтобы избежать записи одного и того же объекта дважды (он просто напишет ссылаясь на «этот объект, который я написал ранее с id x ").

Это означает, что вы эффективно протекаете all ArrayList istances.

Вы можете сбросить этот «кеш», вызвав reset() на ваш ObjectOutputStream . Так как вы все равно не используете , используя эту функцию, вы можете вызвать reset() непосредственно после вызова writeObject() .

    
ответ дан Joachim Sauer 21.09.2011 в 07:59
источник
  • +1 Я не понимал, что это так, кажется, противоречит интуиции, поскольку потоки, как правило, используются для предотвращения использования слишком большого объема памяти. –  Bringer128 21.09.2011 в 08:10
  • @ Bringer128: да, это неочевидная функция, но очень важная: если это не так, сериализация большого дерева объектов может очень легко закончиться бесконечным циклом (подумайте о круговых ссылках). –  Joachim Sauer 21.09.2011 в 08:11
  • Интересно, можно ли это сделать с помощью WeakReferences. Теоретически объект, который записывается, не может выпасть из области действия до тех пор, пока не будет возвращен writeObject. –  Bringer128 21.09.2011 в 08:25
  • @ Bringer128: да, это было бы возможно. Причина этого не была, вероятно, в том, что ObjectOutputStream был введен в Java 1.1, но WeakReference был представлен в Java 1.2. И изменение реализации ObjectOutputStream на использование WeakReference, несомненно, будет изменением. –  Joachim Sauer 21.09.2011 в 08:31
  • Здесь есть, возможно, соответствующая дискуссия: java.net/node/673085 –  Joachim Sauer 21.09.2011 в 08:32
Показать остальные комментарии
4

Я согласен с @Joachim.

Ниже предложение было мифом

Кроме того, рекомендуется (в хорошем соглашении) не объявлять объект внутри цикла . Вместо этого объявите его непосредственно перед запуском цикла и используйте ту же ссылку для инициализации. Это потребует, чтобы ваш код использовал одну и ту же ссылку для каждой итерации и уменьшал нагрузку на поток освобождения памяти (т. Е. Сбор мусора).

Истина
У меня есть отредактированный , потому что я чувствую, что может быть много людей, которые (как и я до сегодняшнего дня) все еще верят, что объявление объекта внутри цикла может нанести вред управлению памятью; что неверно. Чтобы продемонстрировать это, я использовал тот же код, который был размещен на stackOverflow для этого .
Ниже приведен фрагмент кода

package navsoft.advskill.test;

import java.util.ArrayList;

public class MemoryTest {

    /**
     * @param args
     */
    public static void main(String[] args) {
        /* Total number of processors or cores available to the JVM */
        System.out.println("Available processors (cores): "
                + Runtime.getRuntime().availableProcessors());
        /*
         * Total amount of free memory available to the JVM
         */
        long freeMemory = Runtime.getRuntime().freeMemory();
        System.out.println("Free memory (bytes): "
                + freeMemory);
        /*
         * This will return Long.MAX_VALUE if there is no preset limit
         */
        long maxMemory = Runtime.getRuntime().maxMemory();
        /*
         * Maximum amount of memory the JVM will attempt to use
         */
        System.out.println("Maximum memory (bytes): "
                + (maxMemory == Long.MAX_VALUE ? "no limit" : maxMemory));
        /*
         * Total memory currently in use by the JVM
         */
        System.out.println("Total memory (bytes): "
                + Runtime.getRuntime().totalMemory());
        final int LIMIT_COUNTER = 1000000;

        //System.out.println("Testing Only for print...");
        System.out.println("Testing for Collection inside Loop...");
        //System.out.println("Testing for Collection outside Loop...");
        //ArrayList<String> arr;
        for (int i = 0; i < LIMIT_COUNTER; ++i) {
            //arr = new ArrayList<String>();
            ArrayList<String> arr = new ArrayList<String>();
            System.out.println("" + i + ". Occupied(OldFree - currentFree): "+ (freeMemory - Runtime.getRuntime().freeMemory()));
        }
        System.out.println("Occupied At the End: "+ (freeMemory - Runtime.getRuntime().freeMemory()));
        System.out.println("End of Test");
    }

}

Результат вывода ясно показывает, что нет разницы в занятии / освобождении памяти, если вы либо объявляете объект внутри, либо вне цикла. Поэтому рекомендуется иметь декларацию как можно меньше. Я благодарю всех экспертов по StackOverflow (особенно @Miserable Variable) за то, что вы мне это посоветовали.

Надеюсь, это тоже уберет ваши сомнения.

    
ответ дан Naved 21.09.2011 в 08:12
источник
  • Я не уверен, что это будет иметь какой-то эффект. На самом деле я очень подозреваю, что компилятор повторно использует один и тот же локальный слот переменной для arrayList в каждой итерации цикла, что сделает его эквивалентным «объявить его вне цикла». –  Joachim Sauer 21.09.2011 в 08:45
  • -1 Что? Рекомендация заключается в том, чтобы всегда объявлять переменную с наименьшим масштабом. Можете ли вы разместить ссылки? –  Miserable Variable 21.09.2011 в 09:12
  • Я думаю, что это преждевременная оптимизация и довольно опасная. Ограничение области имеет известные преимущества, время было тогда, когда в C все переменные были (должны были быть) объявлены в верхней части функции. Мы этого больше не делаем. См. Stackoverflow.com/questions/377763/.... Я бы настоятельно рекомендовал вам изменить соглашение о кодировании. –  Miserable Variable 21.09.2011 в 09:54
  • Спасибо @HemalPandya. Я отредактировал свой ответ; любезно взгляните на него. –  Naved 22.09.2011 в 09:49
  • Хорошая работа Naved. Я изменил свой downvote на upvote :) Если есть одна вещь, которую я узнал после многих лет программирования, мы все продолжаем делать ошибки. То, что делает хорошего программиста, - это тот, кто готов исправить свою ошибку. –  Miserable Variable 22.09.2011 в 10:38
Показать остальные комментарии