Что делает горячее развертывание "сложной проблемой"?
На работе у нас возникла проблема с исключениями " PermGen out of memory ", и руководитель группы решил, что это ошибка в JVM - что-то, связанное с горячим развертыванием кода. Не объясняя многих деталей, он указал, что горячее развертывание - это "сложная проблема", настолько сложная, что даже.NET пока этого не делает.
Я нашел много статей, объясняющих горячее развертывание с высоты птичьего полета, но всегда испытывал недостаток в технических деталях. Может ли кто-нибудь указать мне техническое объяснение и объяснить, почему горячее развертывание является "сложной проблемой"?
4 ответа
Когда класс загружается, различные статические данные о классе сохраняются в PermGen. Пока существует живая ссылка на этот экземпляр класса, экземпляр класса нельзя собирать мусором.
Я полагаю, что часть проблемы связана с тем, должен ли GC удалять старые экземпляры Class из perm gen или нет. Как правило, каждый раз, когда вы выполняете горячее развертывание, новые экземпляры классов добавляются в пул памяти PermGen, а старые, которые теперь не используются, обычно не удаляются. По умолчанию виртуальные машины Sun не запускают сборку мусора в PermGen, но это можно включить с помощью необязательных аргументов команды "java".
Таким образом, если вы развернете достаточно много раз, вы в конечном итоге исчерпаете пространство PermGen.
Если ваше веб-приложение не закрывается полностью, когда оно не развернуто - например, если оно оставляет работающий поток - тогда все экземпляры Class, используемые этим веб-приложением, будут закреплены в пространстве PermGen. Вы повторно развернули и теперь у вас есть еще одна полная копия всех этих экземпляров Class, загруженная в PermGen. Вы удаляете, и поток продолжает работать, закрепляя ДРУГОЙ набор экземпляров классов в PermGen. Вы перераспределяете и загружаете целый набор копий... и в итоге ваш PermGen заполняется.
Иногда это можно исправить с помощью:
- Предоставление аргументов команды для недавней Sun JVM для включения GC в PermGen и классов. То есть:
-XX:+UseConcMarkSweepGC -XX:+CMSClassUnloadingEnabled -XX:+CMSPermGenSweepingEnabled
- Использование другой JVM, в которой не используется PermGen фиксированного размера или GC для загруженных классов.
Но это поможет, только если ваше веб-приложение завершит работу полностью и чисто, не оставив живых ссылок ни на один из экземпляров Class какого-либо класса, загруженного загрузчиками классов для этого веб-приложения.
Даже это не обязательно решит проблему из-за утечек из загрузчика классов. (А также слишком много интернированных строк в некоторых случаях.)
Проверьте следующие ссылки для большего количества (у двух выделенных жирным шрифтом диаграмм, чтобы проиллюстрировать часть проблемы).
В общих чертах проблема заключается в модели безопасности Java, которая фактически пытается предотвратить повторную загрузку уже загруженного класса.
Конечно, Java с самого начала поддерживала динамическую загрузку классов, но трудно перезагружать классы.
Было сочтено вредным (и по уважительной причине), что запущенное Java-приложение было внедрено с новым классом с вредоносным кодом. Например, взломанная реализация java.lang.String, поступающая из Интернета, которая вместо создания строки удаляет некоторый случайный файл, вызывая метод length().
Таким образом, способ, которым Java был задуман (и я предполагаю, что.NET CLR в последствии, потому что он был очень "вдохновлен" в JVM), состоял в том, чтобы предотвратить загрузку уже загруженного класса той же самой ВМ.
Они предложили механизм для переопределения этой "функции". Загрузчики классов, но опять-таки правила для загрузчиков классов заключались в том, что они должны запросить разрешение у "родительского" загрузчика классов, прежде чем пытаться загрузить новый класс, если родитель уже загрузил класс, новый класс игнорируется.
Например, я использовал загрузчики классов, которые загружают классы из LDAP или RDBMS
Горячее развертывание становится необходимостью в мире Java, когда сервер приложений стал основным для Java EE (а также создает потребность в микроконтейнерах, таких как spring, чтобы избежать такого рода нагрузки).
Перезапуск всего сервера приложений после каждой компиляции сводит с ума любого.
Таким образом, провайдер сервера приложений, предлагает этот "пользовательский" загрузчик классов, чтобы помочь горячему развертыванию, и используя файл конфигурации, такое поведение ДОЛЖНО быть отключено при установке в производство. Но вы должны использовать тонны памяти при разработке. Поэтому хороший способ сделать это - перезапускать каждые 3 - 4 развертывания.
Этого не происходит с другими языками, которые были разработаны с самого начала для загрузки своих классов.
Например, в Ruby вы можете даже добавить методы к запущенному классу, переопределить метод во время выполнения или даже добавить один метод к уникальному конкретному объекту.
Компромисс в таких средах - это, конечно, память и скорость.
Надеюсь, это поможет.
РЕДАКТИРОВАТЬ
Некоторое время назад я обнаружил, что этот продукт обещает сделать перезагрузку максимально простой. Я не помню ссылку, когда я впервые написал этот ответ, и я знаю.
В Sun JVM исправлено пространство PermGen, и в конечном итоге оно все используется (да, по-видимому, из-за ошибки в коде, связанном с загрузчиком классов) => OOM.
Если вы можете использовать JVM другого поставщика (например, Weblogic), он динамически расширяет пространство PermGen, поэтому вы никогда не получите OOM, связанный с permgen.
Какую версию Java вы используете? В ранней версии Sun 1.4.2 были ошибки, но они работали очень долго.
Кстати, как вы будете сообщать новости руководителю вашей команды? Вы руководите командой?