2013-06-11 3 views
15

Я установил максимальную кучу до 8 ГБ. Когда моя программа начнет использовать около 6,4 ГБ (как указано в VisualVM), сборщик мусора начинает занимать большую часть процессора, а программа вылетает с OutOfMemory при распределении ~ 100 МБ. Я использую Oracle Java 1.7.0_21 в Windows.Почему я получаю OutOfMemory, когда 20% кучи все еще бесплатно?

Вопрос в том, есть ли варианты GC, которые помогут с этим. Я ничего не пропускаю, кроме -Xmx8g.

Мое предположение, что куча становится фрагментированной, но не должна ли GC компактно?

+0

Имеет ли ваша виртуальная машина вашего хоста столько RAM? Пример: ваша виртуальная машина (ПК) вашего хоста имеет 4 ГБ памяти, но вы даете гостевой VM 10 ГБ памяти. Я не знаю, будет ли пейджинг, но мне просто интересно узнать характеристики вашей хост-системы. –

+0

Моя машина имеет 10 ГБ оперативной памяти плюс 10 ГБ виртуальной памяти. –

ответ

3

Сбор обрывки информации (что удивительно трудно, так как official documentation очень плохо), я определяется ...

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

Сборщик мусора CMS не выполняет уплотнения, в то время как другие (серийные, параллельные, параллельные и G1). Сборщик по умолчанию в Java 8 - ParallelOld.

Все сборщики мусора разделяют память на регионы, и, AFAIK, все они слишком ленивы, чтобы очень стараться предотвратить ошибку OOM. Опция командной строки -XX:+PrintGCDetails очень полезна для некоторых из сборщиков, показывающих размеры регионов и сколько свободного места у них есть.

Можно экспериментировать с различными сборщиками мусора и настройками. Что касается моего вопроса, сборщик G1 (с флагом JVM -XX:+UseG1GC) решил проблему, с которой я столкнулся. Тем не менее, это было в основном до шанса (в других ситуациях, это OOM быстрее). Некоторые из сборщиков (серийный, cms и G1) имеют широкие возможности настройки для выбора размеров различных регионов, чтобы вы могли бесполезно тратить время, пытаясь решить проблему.

В конечном счете, реальные решения довольно неприятны. Во-первых, нужно установить больше оперативной памяти. Во-вторых, это использование меньших массивов. В-третьих, следует использовать ByteBuffer.allocateDirect. Прямые байтовые буферы (и их int/float/double wrappers) являются подобными массиву объектами с массивной производительностью, которые выделяются в родной куче ОС. Куча ОС использует аппаратное обеспечение виртуальной памяти процессора и не имеет проблем с фрагментацией и даже может эффективно использовать пространство подкачки диска (позволяющее выделять больше памяти, чем доступная оперативная память). Однако большой недостаток заключается в том, что JVM действительно не знает, когда следует освобождать прямые буферы, делая этот вариант более желательным для долгоживущих объектов. Конечным, возможно лучшим и, безусловно, самым неприятным вариантом является выделение и освобождение памяти изначально с использованием вызовов JNI и использование его в Java путем его упаковки в ByteBuffer.

+0

Мышление вызывающий пост. –

0

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

Редактировать: Насколько я знаю, вы не можете заставить Java упаковать память, чтобы вы могли использовать эту полную 8 ГБ, так как это потребовало бы, чтобы сборщик мусора должен был приостановить все остальные потоки , перемещая их объекты вокруг, обновляя все ссылки на эти объекты, затем обновляя устаревшие записи кеша и т. д. ... очень и очень дорогостоящую операцию.

Смотреть это о Memory Fragmentation

+0

-1 Многие сборщики мусора Java (и другие) делают уплотнение (как я выяснил). Но вы правы, это очень дорогостоящая операция, и все потоки должны быть приостановлены (что называется STW или Stop The World). Но поскольку это неинтерактивная программа, пауза не является проблемой. –

2

сборщика мусора вы используете? CMS не делает никакого уплотнения. Попробуйте использовать новый G1 garbage collector - это делает некоторое уплотнение.

Для немного контекста: в G1 сборщик мусора, или `Garbage First» коллектор распадается на кучу на куски и после идентификации (маркировки) весь мусор будет эвакуировать кусок, скопировав все живые биты в другой кусок - это то, что достигает уплотнения.

Для использования включают опцию -XX:+UseG1GC

This дает большое объяснение G1 и сборке мусора в Java в целом.

+0

Я использую настройки по умолчанию, какими бы они ни были. Я попробую это. Кажется, что мне нужно. Единственное, что низкая задержка для меня не является приоритетом. Пропускная способность важнее. Должен ли я использовать G1? –

+0

Используя G1, VisualVM сообщает, что максимальная куча равна физической памяти. Это ошибка в VisualVM, или G1 не заботится о -Xmx? –

+0

Вероятно, по умолчанию используется CMS (который не имеет уплотнения). У G1 есть другие приятные свойства, поэтому я бы попробовал это. Но вы также можете попробовать Parallel collector (это остановить мир, но параллельно) с помощью команды '-XX: + UseParallelOldGC' - это делает уплотнение. – selig

2

Всякий раз, когда эта проблема появляется в прошлом, фактическая свободная память была намного ниже, чем она появилась. Вы можете распечатать объем свободной памяти при возникновении OutOfMemoryError.

try { 
    byte[] array = new byte[largeMemorySize]; 

} catch(OutOfMemroyError e) { 
    System.out.printf("Failed to allocate %,d bytes, free memory= %,d%n", 
     largeMemorySize, Runtime.getRuntime().freeMemory()); 
    throw e; 
} 
+0

Как память «кажется» вам свободна? Количество потенциально доступной памяти на самом деле не 'freeMemory()', а 'Runtime.getRuntime(). MaxMemory() - Runtime.getRuntime(). TotalMemory + Runtime.getRuntime(). FreeMemory()', но далее уменьшается на различные факторы, которые я поднимаю в своем ответе. –

+0

@AleksandrDubinsky freeMemory() не является свободной памятью, если только вы не достигли максимального объема памяти, который вы, вероятно, сделали бы на OOME. Это не гарантируется. Например, если вы запрашиваете больше памяти, чем размер вашей кучи, вы можете вообще не запускать GC. –

0

-Xmx гарантирует только то, что куча не будет превышать 8 Гб в размере, но не дает никаких гарантий, что этот объем памяти на самом деле будет выделено. У вашей машины всего 8 ГБ памяти?

+0

Моя машина имеет 10 ГБ, плюс еще 10 ГБ в виртуальной памяти. Куча уже выделена на 8 ГБ. –

+1

Это просто слишком близко. Куча Java не такая же, как максимальная память, потребляемая Java, она будет больше. Современные машины являются «либеральными» с их использованием памяти, посвящение этой большой части машины в кучу, вероятно, слишком много. Никогда НИКОГДА (как в когда-либо) не позволяйте свопиться к JVM.Вы будете ненавидеть себя, свою семью, своих коллег и членов их семей. Подмена JVM - это смерть. Не делай этого. Даже не позволяйте ему приблизиться. GC и swap НЕ смешиваются. –

Смежные вопросы