Ответ 1
Сбор битов и частей информации (что удивительно сложно, так как официальная документация неплохая), я определил...
Как правило, это может быть две причины, связанные с фрагментацией свободного пространства (т.е. свободное пространство, существующее в небольших кусках, такое, что нельзя выделить большой объект). Во-первых, сборщик мусора может не делать уплотнения, то есть он не дефрагментирует память. Даже коллекционер, который делает уплотнение, может не делать это отлично. Во-вторых, сборщик мусора обычно разбивает область памяти на регионы, которые он резервирует для разных видов объектов, и может не думать о том, чтобы освободить память из региона, который он должен предоставить региону, который в ней нуждается.
Сборщик мусора 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
.