Какие бывают типы OutOfMemoryError или из каких частей состоит память java процесса

    Если вы словили OutOfMemoryError, то это вовсе не значит, что ваше приложение создает много объектов, которые не могут почиститься сборщиком мусора и заполняют всю память, выделенную вами с помощью параметра -Xmx. Я, как минимум, могу придумать два других случая, когда вы можете увидеть эту ошибку. Дело в том, что память java процесса не ограничивается областью -Xmx, где ваше приложение программно создает объекты.

    image



    Область памяти, занимаемая java процессом, состоит из нескольких частей. Тип OutOfMemoryError зависит от того, в какой из них не хватило места.

    1. java.lang.OutOfMemoryError: Java heap space


    Не хватает место в куче, а именно, в области памяти в которую помещаются объекты, создаваемые программно в вашем приложении. Размер задается параметрами -Xms и -Xmx. Если вы пытаетесь создать объект, а места в куче не осталось, то получаете эту ошибку. Обычно проблема кроется в утечке памяти, коих бывает великое множество, и интернет просто пестрит статьями на эту тему.

    2. java.lang.OutOfMemoryError: PermGen space


    Данная ошибка возникает при нехватке места в Permanent области, размер которой задается параметрами -XX:PermSize и -XX:MaxPermSize. Что там лежит и как бороться с OutOfMemoryError возникающей там, я уже описал подробнейшим образом тут.

    3. java.lang.OutOfMemoryError: GC overhead limit exceeded


    Данная ошибка может возникнуть как при переполнении первой, так и второй областей. Связана она с тем, что памяти осталось мало и GC постоянно работает, пытаясь высвободить немного места. Данную ошибку можно отключить с помощью параметра -XX:-UseGCOverheadLimit, но, конечно же, её надо не отключать, а либо решать проблему утечки памяти, либо выделять больше объема, либо менять настройки GC.

    4. java.lang.OutOfMemoryError: unable to create new native thread


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

    На самом деле это очень просто воспроизвести на windows на 32-битной машине, так как там процессу выделяется не больше 2Гб.

    Допустим у вас есть приложение с большим количеством одновременно работающих пользователей, которое запускается с параметрами -Xmx1024M -XX:MaxPermSize=256M -Xss512K. Если всего процессу доступно 2G, то остается свободным еще коло 768M. Именно в данном остатке памяти и создаются стеки потоков. Таким образом, примерно вы можете создать не больше 768*(1024/512)=1536 (у меня при таких параметрах получилось создать 1316) нитей (см. рисунок в начале статьи), после чего вы получите OutOfMemoryError. Если вы увеличиваете -Xmx, то количество потоков, которые вы можете создать соответственно уменьшается. Вариант с уменьшением -Xss, для возможности создания большего количества потоков, не всегда выход, так как, возможно, у вас существуют в системе потоки требующие довольно больших стеков. Например, поток инициализации или какие-нибудь фоновые задачи. Но все же выход есть. Оказывается при программном создании потока, можно указать размер стека: Thread(ThreadGroup group, Runnable target, String name,long stackSize). Таким образом вы можете выставить -Xss довольно маленьким, а действия требующие больших стеков, выполнять в отдельных потоках, созданных с помощью упомянутого выше конструктора.

    Более подробно, что же лежит в стеке потока, и куда уходит эта память, можно прочитать тут.

    Конечно, вам может показаться данная проблема слегка надуманной, так как большинство серверов нынче крутиться на 64-битной архитектуре, но все же считаю данный пример весьма полезным, так как он помогает разобраться из каких частей состоит память java-процесса.
    Метки:
    Поделиться публикацией
    Похожие публикации
    Комментарии 39
    • –3
      Вот тебе бабушка и garbage collector.
      • +15
        Наличие GC не освобождает от обязанности задействовать мозг при написании кода.
        • –3
          Приведите пример программного кода в Java для принудительной очистки памяти?
          • –1
            System.gc();?
            • +2
              Методы System.gc() и Runtime.gc() выглядят, как «запустить сборщик мусора», но и на них нельзя полагаться, поскольку другие более приоритетные потоки могут отложить их выполнение. И на самом деле, документация для методов gc() гласит: «Вызов этих методов подразумевает, что JVM приложит усилия для переработки неиспользуемых объектов».
                • +2
                  Вчера на javaone говорили, что реализация HotSpot JVM с большим пристрастием относится к этим указаниям, и можно быть уверенным, что она, действительно, очень быстро запустит сборку мусора.
              • 0
                Приведите пример программного кода в Java для принудительной очистки памяти?
                Нет такого кода, да и речь не о том. Даже с GC от программиста сильно зависит насколько рационально будет использоваться память и как часто будет вызываться GC.
                • 0
                  Принудительно не очистите. Но помочь можете. За null-айте массивные долгоживущие объекты когда они вам не нужны. Ему станет полегче)
            • 0
              Интересно пишете.
              • +1
                на машинах с 32-битной архитектурой. Особенно на windows, где по умолчанию, память на один процесс ограничена двумя гигабайтами.
                Почему «особенно»? А где по другому?
                • 0
                  На UNIX больше можно. Сколько точно не знаю, но там только один хип (-Xmx) можно выдавать до 3G.
                  • 0
                    в винде с флагом /3GB тоже 3 будет
                    • 0
                      не будет, см мой коммент ниже
                    • –1
                      Ну так больше 4G процесс не может адресовать физически, какой бы чудесной система ни была) А сколько в чистом отдать процессу за вычетом системы — это уже от ОС только зависит. И в винде можно отдать 3+ гб и в линуксах.
                      • 0
                        Бтв, где-то читал, что на «родной» solaris под джаву можно выделить практически полные 4Гб (типа 3.9 там было)
                      • +8
                        Дело в том, что JVM требует непрерывный кусок виртуальной памяти, а винда по легаси причинам мапит в вирт память в районе отметки 3Гб всякий хлам: куски ядра, видео память и т.п. Поэтому память для JVM можно выделить только от конца ядра и до 3Гб. Именно поэтому под виндой трудно создать джава машину с хипом более 1.2-1.4Гб.
                  • 0
                    Размер задается параметрами -Xms и -Xmx.

                    Возможно, -Xmn на рисунке — это опечатка, и должно значиться -Xms?
                    • +4
                      Xms это память с которой джвм стартует. Xmn на картинке — это размер young generation, он к теме отношения в общем-то не имеет, т.к. указывает внутреннюю структуру heap (какую часть хип выделать для GC на т.н. young generation)
                      • 0
                        Всё ясно, спасибо.
                    • 0
                      Вопрос к автору: а в пункте 4 вы не рассматривали вариант с пулом потоков? Или он вам не подошёл?
                      • +1
                        Ну у меня вебсервер был. Тогда еще Servlet API 3.0 не было. Так что пул добавить для асинхронной обработки каких-то действий пользователя я не мог.

                        Хотя на самом деле томкат-то использует внути пул, чтобы обрабатывать запросы клиентов. Просто если он маленький, а клиентов много, то они начинают отваливаться по HTTP с таймаутом, поэтому размер этого пула и пришлось увеличивать все больше и больше.
                      • 0
                        У нас на build-сервере tomcat 6.0.30, ежедневно он валится с PermGen Space.
                        Мы считаем, что это от постоянных редеплоев аппликух. Доктор, это лечится? :)
                        • 0
                          Я же давал ссылку в на свой блок в этой статье в пункте про PermGen, где подробно описывается почему это может происходить и что с этим делать.

                          Конечно каждый конкретный случай надо разбирать отдельно. Если же не хочется разбираться, профайлить и искать причину, чтобы её фиксить, то если у вас CMS, попробуйте -XX:+CMSPermGenSweepingEnabled
                          -XX:+CMSClassUnloadingEnabled


                          Или еще вариант — возьмите JRocket (попробовать можно бесплатно), там говорят вообще проблем нет.

                          Или дождитесь пока oracle JRocket смержит в HotSpot.
                          • 0
                            JRockit, а так плюс
                            • +1
                              CMSPermGenSweepingEnabled устаревший параметр. Достаточно -XX:+CMSClassUnloadingEnabled

                              Неправда, что в JRockit нет этой проблемы. Просто она проявляется позже. Если Hotspot ограничивает размер class metadata, то JRockit позволяет забивать невыгружаемыми классами всю доступную виртуальную память. Однако если утечка памяти есть, она в любом случае проявится рано или поздно, причем в случае исчерпания виртуальной памяти последствия могут крайне неприятными: тормоза, уход в своп, блокировка работы других процессов на машине.

                              В принципе, уже можно потихоньку начинать радоваться избавлению Hotspot'а от PermGen. Уже сейчас вынесены из PermGen в C-heap symbols, interned strings и static fields. Впрочем, спорное решение, на мой взгляд.
                              • 0
                                > CMSPermGenSweepingEnabled устаревший параметр. Достаточно -XX:+CMSClassUnloadingEnabled

                                Правильно, надо или нет добавлять CMSPermGenSweepingEnabled зависит от версии Java. Тем кто еще сидит на пятерке вполне может понадобиться.

                                В JRockit я не силен. Просто об этом сказали на javaone. За что купил, за то и продаю.
                            • +1
                              Возможно у вас проблемы с log4j на томкате. Погуглите log4j tomcat permgen. В ранних версиях томката класслоудер не освобождал классы log4j при андеплое, что приводило к увеличению их, когда приложуха несколько раз передеплоивалась
                            • 0
                              если есть настроение — нужно просто посмотреть, что туда лоадится, а потом не анлоадится. Попробуйте ключи -XX:+TraceClassLoading -XX:+TraceClassUnloading. Другой кандидат — intern строк.
                            • НЛО прилетело и опубликовало эту надпись здесь
                              • +2
                                Формально вы конечно же правы. Хотя никто не будет спорить, что доминирующая реализация — это HotSpot, так что, если ничего об этом не написано, то стоит предполагать что имеется ввиду именно она.
                              • +1
                                это не все варианты — есть еще нехватка direct buffers, которая по своему выглядит и проявляется
                                • 0
                                  Я не спорю, что нет других случаев. Я написал только о тех с которыми сам сталкивался. Возможно я не очень явно это в топике обозначил…

                                Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.