Почему я получаю OutOfMemoryError при выделении структуры данных, которая должна удачно вписаться в кучу, предоставленную для JVM? Это был вопрос, с которым я недавно столкнулся.
Действительно, при взгляде на то, что разработчик пытался выполнить, и тройной проверке размера кучи, предоставленной JVM через параметр -Xmx , действительно показалось, что происходит что-то неясное.
Через 30 минут мы поняли ситуацию и разгадали тайну. Но это действительно было неочевидно с первого взгляда, поэтому я подумал, что это может спасти кого-то за день, если я опишу основную проблему более подробно.
Как всегда, лучший способ понять проблему — это на собственном примере. Я построил небольшой синтетический тестовый пример:
1
2
3
4
5
6
|
package eu.plumbr.demo; class ArraySize { public static void main(String... args) { int [] array = new int [ 1024 * 1024 * 1024 ]; } } |
Код прост — все, что он пытается сделать, — это выделить массив из одного миллиарда элементов. Теперь, учитывая, что примитивам java int требуется 4 байта, можно подумать, что выполнение кода с кучей 6g будет работать нормально. В конце концов, эти миллиарды целых должны занимать только 4 г памяти. Итак, почему я вижу следующее, когда я выполняю код?
1
2
3
|
My Precious:bin me$ java –Xms6g –Xmx6g eu.plumbr.demo.ArraySize Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at eu.plumbr.demo.ArraySize.main(ArraySize.java: 6 ) |
Прежде чем просто добавить еще больше кучи (на самом деле, с –Xmx7g приведенный выше пример работает просто отлично), давайте попробуем понять, почему наши ожидания оказались неверными.
Во-первых, для примитивов int в java действительно требуется 4 байта. Так что не похоже, что наша реализация JVM сошла с ума за ночь. И я могу заверить вас, что математика также верна — 1024 * 1024 * 1024 int примитивам действительно потребуется 4 294 967 296 байт или 4 гигабайта.
Чтобы понять, что происходит, давайте запустим тот же случай и включим ведение журнала сбора мусора, указав –XX: + PrintGCDetails:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
My Precious:bin me$ java –Xms6g -Xmx6g -XX:+PrintGCDetails eu.plumbr.demo.ArraySize -- cut for brevity -- Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at eu.plumbr.demo.ArraySize.main(ArraySize.java: 6 ) Heap PSYoungGen total 1835008K, used 125829K [ 0x0000000780000000 , 0x0000000800000000 , 0x0000000800000000 ) eden space 1572864K, 8 % used [ 0x0000000780000000 , 0x0000000787ae15a8 , 0x00000007e0000000 ) from space 262144K, 0 % used [ 0x00000007e0000000 , 0x00000007e0000000 , 0x00000007f0000000 ) to space 262144K, 0 % used [ 0x00000007f0000000 , 0x00000007f0000000 , 0x0000000800000000 ) ParOldGen total 4194304K, used 229K [ 0x0000000680000000 , 0x0000000780000000 , 0x0000000780000000 ) object space 4194304K, 0 % used [ 0x0000000680000000 , 0x0000000680039608 , 0x0000000780000000 ) PSPermGen total 21504K, used 2589K [ 0x000000067ae00000 , 0x000000067c300000 , 0x0000000680000000 ) object space 21504K, 12 % used [ 0x000000067ae00000 , 0x000000067b087668 , 0x000000067c300000 ) |
Ответы теперь смотрят нам прямо в глаза: даже несмотря на то, что у нас достаточно общей кучи, ни одна отдельная область в куче не является достаточно большой, чтобы вместить 4 г объектов. Наша 6-граммовая куча разделена на четыре отдельные области, имеющие следующие размеры:
- Эдем 1,536M
- Пространства выживших ( от и до ) 256M каждый
- OldGen 4,096M
Теперь, учитывая, что распределение объектов должно соответствовать одному региону, мы действительно можем видеть, что у приложения нет шансов — в любой из областей кучи недостаточно места для размещения этого единственного распределения 4g.
Итак, наша единственная надежда теперь увеличить кучу дальше? Даже если у нас уже есть избыточное обеспечение почти на 50% — передача 6 г кучи структуре данных, которая должна вписаться в 4 г? Не так быстро — есть альтернативное решение. Вы можете установить размер различных областей в памяти. Это не так просто и удобно для пользователя, как можно было ожидать, но две небольшие модификации конфигурации запуска помогут. При запуске одного и того же кода всего две дополнительные опции:
1
|
My Precious:bin me$ java -Xms6g -Xmx6g -XX:NewSize=5g -XX:SurvivorRatio= 10 eu.plumbr.demo.ArraySize |
тогда программа выполняет свою работу, и OutOfMemoryError не генерируется. Добавление -XX: + PrintGCDetails к автозагрузке также объясняет это:
1
2
3
4
5
6
7
8
9
|
Heap PSYoungGen total 4806144K, used 4369080K [ 0x00000006c0000000 , 0x0000000800000000 , 0x0000000800000000 ) eden space 4369408K, 99 % used [ 0x00000006c0000000 , 0x00000007caaae228 , 0x00000007cab00000 ) from space 436736K, 0 % used [ 0x00000007e5580000 , 0x00000007e5580000 , 0x0000000800000000 ) to space 436736K, 0 % used [ 0x00000007cab00000 , 0x00000007cab00000 , 0x00000007e5580000 ) ParOldGen total 1048576K, used 0K [ 0x0000000680000000 , 0x00000006c0000000 , 0x00000006c0000000 ) object space 1048576K, 0 % used [ 0x0000000680000000 , 0x0000000680000000 , 0x00000006c0000000 ) PSPermGen total 21504K, used 2563K [ 0x000000067ae00000 , 0x000000067c300000 , 0x0000000680000000 ) object space 21504K, 11 % used [ 0x000000067ae00000 , 0x000000067b080c90 , 0x000000067c300000 ) |
Мы видим, что размеры регионов сейчас действительно то, что мы просили:
- Размер младшего целого (eden + два пространства выживших) равен 5g, как указано в параметре -XX: NewSize = 5g
- Eden в 10 раз больше, чем выживший, как мы указали с параметром -XX: SurvivorRatio = 10 .
Обратите внимание, что в нашем случае оба параметра были необходимы. Если указать только -XX: NewSize = 5g, он все равно будет разделен между eden и оставшимися в живых так, что ни одна отдельная область не сможет удержать требуемые 4g.
Надеюсь, что чтение этого объяснения сэкономит вам день отладки в будущем. Или помочь вам избежать чрезмерного выделения ресурсов.
Ссылка: | OutOfMemoryError в переполненной куче от нашего партнера по JCG Владимира Сора в блоге Plumbr Blog . |