Java из кучи во время сериализации
Следующий код вызывает 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 из-за кучи.
Итак, почему возникает исключение?
Ответы
Ответ 1
Является objOS
a ObjectOutputStream
?
Если это так, то ваша проблема: ObjectOutputStream
хранит сильную ссылку на каждый объект, который когда-либо был написан для этого, чтобы избежать записи одного и того же объекта дважды (он просто напишет ссылку, говорящую "этот объект, который я писал ранее с id x" ).
Это означает, что вы эффективно просачиваете все теги ArrayList
.
Вы можете reset "кеш", вызвав reset()
на ObjectOutputStream
. Поскольку вы все равно не используете эту функцию, вы можете вызвать reset()
непосредственно после вызова writeObject()
.
Ответ 2
Я согласен с @Joachim.
Ниже предложение было мифом
Кроме того, рекомендуется (в хорошем соглашении) не объявлять какой-либо объект внутри цикла. Вместо этого объявите его непосредственно перед запуском цикла и используйте ту же ссылку для инициализации. Это потребует, чтобы ваш код использовал одну и ту же ссылку для каждой итерации и уменьшал нагрузку на поток выпуска памяти (т.е. Сбор мусора).
Истина
У меня есть отредактированный, потому что я чувствую, что может быть много людей, которые (как и я до сегодняшнего дня) все еще верят, что объявление объекта внутри цикла может нанести вред управлению памятью; что неверно.
Чтобы продемонстрировать это, я использовал тот же код, который был отправлен на fooobar.com/info/31230/....
Ниже приведен фрагмент кода
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) за то, что вы мне это посоветовали.
Надеюсь, это тоже уберет ваши сомнения.