Пользователь
0,0
рейтинг
14 апреля 2011 в 09:02

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

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-процесса.
Тёма @javaspecialist
карма
68,2
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (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
          Я тогда абзац про флаг в windows из своей статьи удаляю? pyatigil, можете дать ссылку про то что вы говорите? Или есть еще эксперты в этой области подтвердить сказанное pyatigil?
  • 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
      Я не спорю, что нет других случаев. Я написал только о тех с которыми сам сталкивался. Возможно я не очень явно это в топике обозначил…

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