百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程网 > 正文

JDK21新特性:Prepare to Disallow the Dynamic Loading of Agents

yuyutoo 2025-05-23 21:13 2 浏览 0 评论

Prepare to Disallow the Dynamic Loading of Agents

JEP 451:准备禁止动态加载代理

摘要

当代理被动态加载到正在运行的 JVM 中时发出警告。这些警告旨在为未来版本做准备,在该版本中,默认情况下将禁止动态加载代理以提高默认完整性。在启动时加载代理的服务工具不会导致任何版本发出警告。

目标

1. 为未来的 JDK 版本做准备,该版本将默认禁止将代理加载到正在运行的 JVM 中。

2. 重新评估服务性(允许对运行中的代码进行临时更改)与完整性(假设运行中的代码不会被任意更改)之间的平衡。

3. 确保大多数不需要动态加载代理的工具不受影响。

4. 将动态加载代理的能力与其他所谓的“超级能力”(如深度反射)保持一致。

Java 平台中的代理

代理是一种可以在应用程序运行时更改其代码的组件。代理最初由 JDK 5 的 Java 平台性能分析架构引入,目的是让工具(特别是性能分析器)能够对类进行插桩。这意味着修改一个类的代码,使其向外部工具发出事件,而不会改变代码的行为。代理通过以下两种方式实现这一点:

- 在类加载期间转换类,

- 或重新定义之前加载的类。

代理可以用 Java 代码编写,使用 `java.lang.instrument` API(称为“Java 代理”),也可以用原生代码编写,使用 JVM 工具接口(称为“JVM TI 代理”)。

代理最初设计的目的是为了进行良性的插桩,即添加插桩不会影响应用程序行为。然而,高级开发者发现了诸如面向切面编程(Aspect-Oriented Programming, AOP)等用例,可以以任意方式更改应用程序行为。此外,代理还可以更改应用程序之外的代码,例如 JDK 本身的代码。为了确保应用程序的所有者同意使用代理,JDK 5 要求代理必须通过命令行选项 `-javaagent` 或 `-agentlib` 指定,并在启动时立即加载。这代表了应用程序所有者的显式授权。

服务性与动态加载代理

服务性是指系统操作员在应用程序运行时对其进行监控、观察、调试和故障排除的能力。Java 平台卓越的服务性一直是引以为傲的特点。

为了支持服务性工具,JDK 6 引入了 **Attach API**。Attach API 不属于 Java 平台,而是 JDK 提供的一个外部使用的 API。它允许具有适当操作系统权限的工具连接到本地或远程的正在运行的 JVM,并与其通信以观察和控制其操作。Attach API 默认启用,但可以通过命令行选项 `-XX:+DisableAttachMechanism` 禁用。

使用 Attach API 的工具示例包括:

1. **监控和管理工具**,例如 `jcmd` 和 `jconsole`,它们可以观察应用程序指标并更改配置。例如,如果应用程序使用 `java.util.logging` API,则操作员可以使用 `jconsole` 动态更改日志级别。这些工具利用专门的 `jcmd` 协议、JMX 和 JDK Flight Recorder (JFR)。

2. **调试器**,需要在启动时通过 `-agentlib:jdwp` 选项启用内置的 JVM 调试代理。它们通过某种 IPC 通道与代理通信,同时也可以利用 Attach API。

3. **性能分析器和 APM 工具**,它们通常会在启动时加载代理以对应用程序代码进行插桩,从而生成 JFR 事件供 JDK Mission Control 或其他客户端消费。

Attach API 还允许工具将代理动态加载到正在运行的 JVM 中。这种能力支持涉及实时更改任意代码的高级用例。动态加载代理的工具示例包括:

1. **性能分析器**,它们连接到正在运行的 JVM 并动态加载代理以对应用程序代码进行插桩。

2. **临时故障排除工具**,它们在运行时读取和写入应用程序状态。动态加载的代理可能使用 JVM TI 检查运行程序的状态,或者转换和插桩已加载的类。

(非常高级的开发者有时会通过编写代理来修补生产环境中的错误代码,并动态加载该代理以修复问题。然而,这不是受支持的用例,也从未被推荐。代理重新定义已加载类的能力受到限制,因此通过修补修复错误的能力有限。此外,代理无法持久化所做的更改,重启应用程序后更改会被还原。)

动态加载的代理赋予服务性工具更改正在运行的应用程序的“超级能力”。然而,附加工具的操作是由具有适当操作系统凭据的人类操作员触发的。这种人为干预授予了更改应用程序的批准,因此服务性工具不受施加于其他代码的完整性约束限制。因此,默认情况下允许动态加载代理,尽管在 JDK 9 及更高版本中,可以通过命令行选项 `-XX:-EnableDynamicAgentLoading` 禁用。

代理与库

尽管库和工具之间存在概念上的分离,但某些库依赖于代理提供的代码更改“超级能力”来实现功能。例如:

- 模拟库可能会重新定义应用程序类以绕过业务逻辑不变量。

- 白盒测试库可能会重新定义 JDK 类,以便始终允许通过反射访问私有字段。

为了获得这些能力,库可以使用代理从 JVM 获取一个全能的 `Instrumentation` 对象,并将其传递给库。

一些这样的库通过要求在命令行中使用 `-javaagent` 选项指定库的代理,确保应用程序所有者授予权限来更改应用程序。例如,早期的 Quasar 库就是后来成为虚拟线程(JEP 444)的原型。

其他库则采取更可疑的方法,在未获得应用程序所有者批准的情况下获取这些能力。它们使用 Attach API 默默地连接到运行它们的 JVM 并动态加载代理,实际上伪装成服务性工具。为了维护完整性,JDK 9 及更高版本默认禁止代码连接到当前 JVM(可以通过 `-Djdk.attach.allowAttachSelf=true` 启用)。然而,这一措施已被证明不足以阻止这些行为:一些库现在会启动第二个 JVM,连接到第一个 JVM 并在那里加载代理,同时加载库本身。

如果某个库使用代理默默地重新定义 JDK 类,从而绕过强封装,则强封装所强制执行的所有不变量都无法再信任。完整性丧失。

总结

在 JDK 21 中,动态加载代理仍然被允许,但当发生时,JVM 会发出警告。例如:

```

WARNING: A {Java,JVM TI} agent has been loaded dynamically (file:/u/bob/agent.jar)

WARNING: If a serviceability tool is in use, please run with -XX:+EnableDynamicAgentLoading to hide this warning

WARNING: If a serviceability tool is not in use, please run with -Djdk.instrument.traceUsage for more information

WARNING: Dynamic loading of agents will be disallowed by default in a future release

```

要让工具能够动态加载代理而不发出警告,用户必须在命令行中使用 `-XX:+EnableDynamicAgentLoading` 选项运行。

使用 `-Djdk.instrument.traceUsage` 运行时,`java.lang.instrument` API 的方法会在被调用时打印一条消息和堆栈跟踪。这有助于识别错误地使用动态加载代理而不是在启动时加载代理的库。鼓励动态加载代理的库维护者更新其文档,描述用户如何在启动时加载代理;各种部署选项由 `java.lang.instrument` API 提供。

在未来的某个版本中,动态加载代理将默认被禁止。开箱即用的情况下,任何使用 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.

```

要在默认禁止动态加载代理的情况下允许动态加载代理,用户必须在命令行中使用 `-XX:+EnableDynamicAgentLoading`。

为了为未来版本中的默认行为变化做准备,JDK 9 或更高版本的用户可以通过在命令行中使用 `-XX:-EnableDynamicAgentLoading` 明确禁止动态加载代理。

使用启动时加载代理的工具不受这些变化的影响。`-javaagent` 选项、`-agentlib` 选项以及 `Launcher-Agent-Class` JAR 文件属性的意义和操作保持不变。

使用 Attach API 但并非用于动态加载代理的工具也不受这些变化的影响。

库不得动态加载代理。需要使用代理的库必须通过 `-javaagent` 或 `-agentlib` 选项在启动时加载代理。

替代方案

1. 默认情况下仅对动态加载的原生 JVM TI 代理发出警告,并默认限制动态加载的 Java 代理的能力(即,当未指定 `-XX:+EnableDynamicAgentLoading` 选项时),使得它们在尝试修改命名模块中的类时发出警告,但在修改未命名模块中的类时不发出警告。

这种方法更为复杂,且并不能显著支持更多实用的工具代理。此外,它并不能阻止 Java 代理通过 JNI 授予自身更多权限。

2. 使用一种身份验证机制,区分人类操作的工具和伪装成工具的库,从而默认允许工具动态加载代理而不发出警告,而在库尝试动态加载代理时发出警告。

我们探索了几种类似的方法,但所有方法要么过于复杂,要么需要在命令行中进行特殊设置,这并不会减少对动态加载代理的工具的影响。

相关推荐

Java开发中如何优雅地避免OOM(OutOfMemoryError)

Java开发中如何优雅地避免OOM(OutOfMemoryError)在这个信息化高速发展的时代,内存就像程序员手中的笔,缺了它就什么都写不出来。而OOM(OutOfMemoryError)就像是横在...

常见的JVM调优方法和步骤

1、内存调优堆内存设置:通过-Xms和-Xmx参数调整初始和最大堆内存大小-Xms:初始堆大小(如-Xms512M)-Xmx:最大堆大小(如-Xmx2048M)调整新生代和老年代的比例...

Java中9种常见的CMS GC问题分析与解决(一)

目前,互联网上Java的...

JDK21新特性:Prepare to Disallow the Dynamic Loading of Agents

PreparetoDisallowtheDynamicLoadingofAgentsJEP451:准备禁止动态加载代理摘要...

Java程序GC垃圾回收机制优化指南

Java程序GC垃圾回收机制优化指南作为一个Java开发者,我们经常会在任务管理器里看到Java进程占用内存不断增长,然后突然下降的现象。这其实就是在Java虚拟机中运行的垃圾回收(GC)机制在起作用...

Java Java命令学习系列(一)——Jps

jps位于jdk的bin目录下,其作用是显示当前系统的java进程情况,及其id号。jps相当于Solaris进程工具ps。不象”pgrepjava”或”ps-efgrepjava”,jps...

面试题专题:头条一面参考答案(003)

前两篇文章也都是介绍头条一面的内容及参考答案...

Java JVM原理与性能调优:从基础到高级应用

一、JVM基础架构与内存模型1.1JVM整体架构概览Java虚拟机(JVM)是Java程序运行的基石,它由以下几个核心子系统组成:...

死锁攻防战:阿里架构师教你用3种核武器杜绝程序僵死

从线程转储分析到银行家算法,彻底掌握大厂必考的死锁解决方案以下是为Java死锁问题设计的结构化技术解析方案,包含代码级解决方案与高频追问应对策略:...

Java 1.8 虚拟机内存分布详解

Java1.8虚拟机内存分布详解Java1.8的JVM内存布局相比早期版本有显著变化(如永久代被元空间取代)。以下是其核心内存区域的划分、作用及配置参数:一、JVM内存整体结构...

Java 多线程开发难题?这篇文章给你答案!

作为互联网大厂的后端开发人员,在Java多线程开发过程中,必然会面临诸多复杂且具有挑战性的问题。在高并发场景下,各类潜在问题对系统的稳定性与性能产生严重影响,本文将深入探讨这些问题,并提供全面且有...

软件性能调优全攻略:从瓶颈定位到工具应用

性能调优是软件测试中的重要环节,旨在提高系统的响应时间、吞吐量、并发能力、资源利用率,并降低系统崩溃或卡顿的风险。通常,性能调优涉及发现性能瓶颈、分析问题根因、优化代码和系统配置等步骤,调优之前需要先...

JVM性能优化实战技巧

JVM性能优化实战技巧在现代企业级应用开发中,JavaVirtualMachine(JVM)作为承载Java应用程序的核心引擎,其性能直接决定了系统的响应速度、吞吐量以及资源利用率。因此,掌握一些...

JVM 深度解析:运行时数据区域、分代回收与垃圾回收机制全攻略

共同学习,有错欢迎指出。JVM运行时数据区域1.程序计数器程序计数器是一块较小的内存空间,可看作当前线程所执行的字节码的行号指示器。在虚拟机概念模型里,字节码解释器通过改变这个计数器的值选取下一条...

JVM内存管理详解与调优实战

JVM内存管理详解与调优实战Java虚拟机(JVM)作为Java程序运行的核心组件,其内存管理机制直接影响着应用程序的性能表现。今天,咱们就来一场既严肃又有趣的JVM内存管理之旅,看看这个“幕后英雄”...

取消回复欢迎 发表评论: