JEP draft: Disallow the Dynamic Loading of Agents by Default
Запрет динамической загрузки агентов по умолчанию
| Ответственный | Ron Pressler |
| Тип | Feature |
| Область | SE |
| Статус | Submitted |
| Компонент | hotspot / svc |
| Обсуждение | jigsaw dash dev at openjdk dot org, serviceability dash dev at openjdk dot org |
| Трудоёмкость | XS |
| Длительность | XS |
| Связан с | JEP 451: Prepare to Disallow the Dynamic Loading of Agents |
| Рецензенты | Alan Bateman |
| Создан | 2026/07/23 17:17 |
| Обновлён | 2026/10/29 08:54 |
| Задача | 8388877 |
Аннотация
По умолчанию запретить динамическую загрузку агентов в работающую JVM, чтобы усилить целостность по умолчанию. Начиная с JDK 21, JVM выдаёт предупреждения, когда агенты загружаются динамически без явного разрешения; это изменение внёс JEP 451. Этот JEP завершает переход. Загрузки агентов при запуске изменение не касается. Диагностических команд, в том числе предоставляемых jcmd, изменение не касается, если только они не загружают агенты динамически.
Цели
-
По умолчанию запретить динамическую загрузку агентов в работающую JVM.
-
Усилить целостность по умолчанию и при этом сохранить те возможности обслуживания, для которых не нужна динамическая загрузка агентов.
-
Привести возможность динамической загрузки агентов в соответствие с другими так называемыми «суперспособностями», такими как глубокая рефлексия.
Что не является целью
-
Цель не в том, чтобы запретить загрузку агентов при запуске JVM с помощью параметров командной строки
-javaagentили-agentlib. -
Цель не в том, чтобы объявить устаревшими или удалить те части Attach API, которые загружают агенты динамически.
-
Цель не в том, чтобы изменить те части Attach API, которые позволяют инструментам обслуживания подключаться к работающей JVM для мониторинга и управления. Такие инструменты, как
jcmdиjconsole, продолжат работать без параметров командной строки.
История
JEP 451: Prepare to Disallow the Dynamic Loading of Agents добавил предупреждение, чтобы подготовить пользователей к выпуску, в котором динамическая загрузка агентов будет по умолчанию заблокирована. Этот JEP завершает этот процесс.
Мотивация
Агенты в платформе Java
Агент — это компонент, который может изменять код приложения во время работы приложения. Агенты появились в JDK 5 вместе с Java Platform Profiling Architecture как способ, с помощью которого инструменты, в частности профилировщики, могут инструментировать классы. Это означает изменять код класса так, чтобы он генерировал события, которые потребляет инструмент вне приложения, но в остальном не менять поведение кода. Агенты делают это либо преобразуя классы во время загрузки классов, либо переопределяя классы, загруженные ранее. Их можно писать на Java с помощью API java.lang.instrument («Java-агенты») или в нативном коде с помощью JVM Tool Interface («агенты JVM TI»).
Агенты проектировались в расчёте на безобидное инструментирование, при котором добавление инструментирования не влияет на поведение приложения. Однако опытные разработчики нашли сценарии использования, такие как аспектно-ориентированное программирование, которые произвольным образом изменяют поведение приложения. Кроме того, ничто не мешает агенту изменять код вне приложения, например код самого JDK. Чтобы гарантировать, что владелец приложения одобрил использование агентов, JDK 5 требовал указывать агенты в командной строке параметрами -javaagent или -agentlib и загружал агенты сразу при запуске. Это было явным предоставлением привилегий со стороны владельца приложения.
Обслуживаемость и динамически загружаемые агенты
Обслуживаемость — это возможность для системного оператора отслеживать, наблюдать, отлаживать приложение и диагностировать проблемы во время его работы. Превосходная обслуживаемость платформы Java давно служит поводом для гордости.
Для поддержки инструментов обслуживания в JDK 6 появился Attach API. Attach API не является частью платформы Java, а представляет собой API JDK, поддерживаемый для внешнего использования. Он позволяет инструменту, запущенному с соответствующими привилегиями операционной системы, подключиться к работающей JVM, локальной или удалённой, и взаимодействовать с этой JVM, чтобы наблюдать за её работой и управлять ею. Attach API включён по умолчанию, но его можно отключить параметром -XX:+DisableAttachMechanism в командной строке.
Примеры инструментов, использующих Attach API:
-
Инструменты мониторинга и управления, такие как
jcmdиjconsole, которые отслеживают метрики приложения и изменяют конфигурацию. Например, если приложение использует APIjava.util.logging, оператор может с помощьюjconsoleдинамически изменить уровень логирования. Эти инструменты используют специализированные протоколыjcmd, JMX и JDK Flight Recorder (JFR). -
Отладчики, которым требуется встроенный в JVM агент, включаемый при запуске параметром
-agentlib:jdwp. Затем они взаимодействуют с агентом по некоторому каналу IPC, но также могут пользоваться возможностями Attach API. -
Профилировщики и, в более общем смысле, инструменты Application Performance Monitoring (APM), которые используют агент, загружаемый при запуске, чтобы инструментировать код приложения так, чтобы он генерировал события JFR для потребления JDK Mission Control или другими клиентами.
Attach API также позволяет инструменту динамически загрузить агент в работающую JVM. Эта возможность поддерживает сложные сценарии использования, связанные с изменением произвольного кода на лету. Примеры инструментов, которые динамически загружают агенты:
-
Профилировщики, которые подключаются к работающей JVM и динамически загружают агент, чтобы инструментировать код приложения.
-
Инструменты ситуативной диагностики, которые читают и изменяют состояние приложения во время выполнения. Динамически загруженный агент либо использует JVM TI для исследования состояния работающей программы, либо преобразует и инструментирует загруженные классы.
(Очень опытные разработчики иногда исправляют ошибки в производственных средах: пишут агент, который исправляет ошибочный код, и загружают этот агент динамически. Однако это не поддерживаемый сценарий использования, и он никогда не рекомендовался. Возможность агента переопределять загруженные классы подвержена ограничениям, поэтому возможность исправлять ошибки таким способом ограничена. Более того, агент не может сохранить сделанные им изменения, поэтому перезапуск приложения отменит изменение.)
Динамически загружаемые агенты дают инструментам обслуживания суперспособность изменять работающее приложение. Однако подключение инструмента инициирует человек-оператор с соответствующими учётными данными операционной системы. Этот человек в контуре управления даёт одобрение на изменение приложения, поэтому на инструменты обслуживания не распространяются ограничения целостности, налагаемые на другой код. Поэтому динамическая загрузка агентов по умолчанию разрешена, хотя в JDK 9 и более поздних версиях её можно запретить параметром -XX:-EnableDynamicAgentLoading в командной строке.
JEP 451: Prepare to Disallow the Dynamic Loading of Agents, вошедший в JDK 21, добавил предупреждение, которое выдаётся, когда агент загружается динамически без параметра -XX:+EnableDynamicAgentLoading. Предупреждение сообщало пользователям, что в одном из будущих выпусков динамическая загрузка агентов будет по умолчанию запрещена. Этот JEP завершает этот переход: динамическая загрузка агентов запрещается по умолчанию.
Агенты и библиотеки
Несмотря на концептуальное разделение ответственности между библиотеками и инструментами, некоторые библиотеки предоставляют функциональность, которая опирается на доступную агентам суперспособность изменять код. Например, библиотека для создания mock-объектов может переопределять классы приложения, чтобы обойти инварианты бизнес-логики, а библиотека для тестирования методом белого ящика может переопределять классы JDK так, чтобы рефлексия над полями private была всегда разрешена. Чтобы получить эти возможности, библиотека может использовать агент, который получает от JVM всемогущий объект Instrumentation и передаёт его библиотеке.
Некоторые из таких библиотек добиваются того, чтобы владелец приложения одобрил изменение приложения: они требуют указывать агент библиотеки в командной строке с помощью параметра -javaagent. Другие библиотеки действуют более сомнительно и получают возможности без одобрения владельца приложения. Они с помощью Attach API незаметно подключаются к JVM, в которой работают, и динамически загружают агенты, фактически выдавая себя за инструменты обслуживания. Чтобы сохранить целостность, JDK 9 и более поздние выпуски по умолчанию не позволяют коду подключаться к текущей JVM. (Такие подключения можно разрешить с помощью -Djdk.attach.allowAttachSelf=true.) Однако этой меры оказалось недостаточно: некоторые библиотеки теперь запускают вторую JVM, которая подключается к первой и загружает агент туда, рядом с библиотекой.
Если библиотека использует агенты, чтобы незаметно переопределять классы JDK, обходя тем самым строгую инкапсуляцию, то ни на один из инвариантов, обеспечиваемых строгой инкапсуляцией, нельзя полагаться. Целостность утрачена.
К целостности по умолчанию
Чтобы гарантировать целостность, нужны меры, которые не позволят библиотекам злоупотреблять динамически загружаемыми агентами. К сожалению, мы не нашли простого и автоматического способа отличить инструмент обслуживания, который динамически загружает агент, от библиотеки, которая динамически загружает агент. Дать полную свободу инструментам означало бы дать полную свободу библиотекам, а это равносильно отказу от целостности по умолчанию.
Поэтому мы требуем, чтобы динамическую загрузку агентов одобрял владелец приложения, — точно так же, как начиная с JDK 5 мы требуем, чтобы загрузку агентов при запуске одобрял владелец приложения. Это изменение приблизит платформу Java к долгосрочной цели — целостности по умолчанию. На практике владельцу приложения придётся самому разрешать динамическую загрузку агентов с помощью параметра командной строки.
К счастью, большинство инструментов обслуживания не зависят от динамически загружаемых агентов. Однако запрет динамической загрузки агентов по умолчанию означает, что приёмы ситуативной диагностики, требующие динамической загрузки агента, больше не будут работать «из коробки». Если возникнет необходимость динамически загрузить агент, JVM нужно будет перезапустить с соответствующим параметром командной строки, чтобы выразить одобрение владельца приложения.
Последствия этого изменения смягчатся тем, что большинство современных серверных приложений спроектированы с резервированием, поэтому отдельные узлы можно при необходимости перезапустить с этим параметром командной строки. Особые случаи — например, JVM, которую никогда нельзя останавливать для обслуживания, или канареечный процесс новой версии ПО, который находится под пристальным наблюдением, — обычно можно определить заранее, чтобы динамическая загрузка агентов была разрешена с самого начала.
Требование к владельцам приложений одобрять динамически загружаемые агенты позволит экосистеме Java воплотить концепцию целостности по умолчанию, не ограничивая существенно обслуживаемость.
Описание
В JDK NN динамическая загрузка агентов по умолчанию запрещена. Попытки динамически загрузить Java-агент или нативный агент JVM TI через Attach API завершаются следующим исключением:
com.sun.tools.attach.AgentLoadException: Failed to load agent library: \
Dynamic agent loading is not enabled. Use -XX:+EnableDynamicAgentLoading \
to launch target VM.
Чтобы разрешить динамическую загрузку агентов, пользователи должны запускать целевую JVM с -XX:+EnableDynamicAgentLoading в командной строке.
Инструментов, которые используют агенты, загружаемые при запуске, изменение не касается. Смысл и работа параметра -javaagent, параметра -agentlib и атрибута JAR-файла Launcher-Agent-Class не меняются.
Инструментов, которые используют Attach API не для динамической загрузки агента, а для других целей, изменение тоже не касается.
Библиотеки, которые используют агенты, должны загружать их при запуске с помощью параметров -javaagent или -agentlib.
Риски и допущения
-
Мы предполагаем, что в большинстве сценариев обслуживания динамическая загрузка агентов не нужна, поэтому изменение их не затронет.
-
Мы предполагаем, что предупреждения, появившиеся в JDK 21, побудили разработчиков библиотек, которые динамически загружают агенты, описать в документации, как владельцы приложений могут загружать эти агенты при запуске или явно разрешить динамическую загрузку. Приложения, которые по-прежнему зависят от динамической загрузки агентов без явного разрешения, могут столкнуться со сбоями при обновлении.
Дальнейшая работа
-
Продвинутые профилировщики, которые профилируют нативный код, могут динамически загружать агенты JVM TI, чтобы получить доступ к механизмам профилирования HotSpot. Этот сценарий можно было бы решить, расширив возможности JFR так, чтобы для такого профилирования не требовался агент.
-
Часть сценариев изменения кода можно было бы обслужить, если предоставлять библиотекам объект
Instrumentation, соблюдающий инкапсуляцию, напрямую, без участия агента. Это позволило бы библиотеке преобразовывать или переопределять классы в модулях, открытых для модуля библиотеки.
Альтернативы
-
По умолчанию запрещать только динамическую загрузку нативных агентов JVM TI, а возможности динамически загружаемых Java-агентов по умолчанию (то есть когда параметр
-XX:+EnableDynamicAgentLoadingне указан) ограничивать так, чтобы при попытке изменить классы в именованных модулях выдавалась ошибка, но изменять классы в безымянных модулях было разрешеноЭтот подход сложнее и не позволяет поддержать существенно больше практически полезных агентов для инструментов. Кроме того, он не мешает Java-агенту использовать JNI, чтобы получить дополнительные возможности.
-
Использовать механизм аутентификации, который отличает инструмент, управляемый человеком, от библиотеки, выдающей себя за инструмент, чтобы по умолчанию разрешать инструменту динамически загружать агент, но запрещать это библиотеке.
Мы изучили несколько подходов в этом направлении, но все они либо были сложными, либо требовали специальной настройки в командной строке, а это не уменьшило бы влияние на инструменты, которые динамически загружают агент.