Java 模块检测
Java instrumentation with modules
我正在尝试在模块化应用程序中 运行 一个 javaagent
,但我无法通过命令行使其工作。我创建了尽可能小的存储库:
.
├── Makefile
├── manifest
└── mods
├── main
│ ├── module-info.java
│ └── tsp
│ └── App.java
└── modifier
├── module-info.java
└── tsp
└── Agent.java
mods/main/module-info.java
module main {
}
mods/main/tsp/App.java
package tsp;
public class App {
public static void main(String[] args) {
}
}
mods/modifier/module-info.java
module modifier {
requires java.instrument;
}
mods/modifier/tsp/Agent.java
package tsp;
import java.lang.instrument.Instrumentation;
public class Agent {
public static void premain(String agentArgs, Instrumentation inst) {
}
}
生成文件
SHELL := /bin/bash
.PHONY: clean build_main build_agent run
build_agent: clean
echo -e "Premain-Class: tsp.Agent\nCan-Retransform-Classes: true\n" > manifest
javac --module-path mods/modifier -d output/modifier $$(find mods/modifier -name *.java) && \
jar --create --file output/modifier.jar --manifest manifest -C output/modifier .
build_main: clean
javac --module-path mods/main -d output/main $$(find mods/main -name *.java)
run: build_main build_agent
java -javaagent:output/modifier.jar --module-path output/main --module main/tsp.App
clean:
rm -rf output
manifest
是从 Makefile
自动创建的。
当我执行make run
时,输出是:
rm -rf output
javac --module-path mods/main -d output/main $(find mods/main -name *.java)
echo -e "Premain-Class: tsp.Agent\nCan-Retransform-Classes: true\n" > manifest
javac --module-path mods/modifier -d output/modifier $(find mods/modifier -name *.java) && \
jar --create --file output/modifier.jar --manifest manifest -C output/modifier .
java -javaagent:output/modifier.jar --module-path output/main --module main/tsp.App
Exception in thread "main" java.lang.ClassNotFoundException: tsp.Agent
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:606)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:168)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:522)
at java.instrument/sun.instrument.InstrumentationImpl.loadClassAndStartAgent(InstrumentationImpl.java:431)
at java.instrument/sun.instrument.InstrumentationImpl.loadClassAndCallPremain(InstrumentationImpl.java:525)
*** java.lang.instrument ASSERTION FAILED ***: "result" with message agent load/premain call failed at open/src/java.instrument/share/native/libinstrument/JPLISAgent.c line: 422
FATAL ERROR in native method: processing of -javaagent failed, processJavaStart failed
make: *** [Makefile:14: run] Aborted (core dumped)
相反,当我将 Makefile
中的 run
目标更改为:
run: build_main build_agent
java -javaagent:output/modifier.jar --class-path output/main tsp.App
一切正常。我不想使用像 Gradle 或 Maven 这样的构建工具,因为我想了解为什么它不能从命令行运行。我读过 Loading agent classes and the modules/classes available to the agent class 但老实说,我并不完全清楚。我做了很多尝试,比如使用 --add-modules output/modifier
但没有成功。
> java --version
openjdk 15.0.2 2021-01-19
OpenJDK Runtime Environment (build 15.0.2+7-27)
OpenJDK 64-Bit Server VM (build 15.0.2+7-27, mixed mode, sharing)
代理不能模块化,它们会自动加载到系统 class 加载程序的 class 路径,进入未命名的模块。
我假设 JVM 没有正确涵盖代理是模块化的并且在尝试调用它时在内部崩溃的情况。
你用最近的 Java 11/17 试过这个吗?我认为这可以通过删除代理的模块描述符来解决。
正如其他答案所说,Java 代理被加载到未命名模块中。这隐藏了您的设置问题。当你 运行 你的模块
java -javaagent:output/modifier.jar --add-modules modifier \
--module-path output/main:output/modifier.jar --module main/tsp.App
你会得到
Error occurred during initialization of boot layer
java.lang.LayerInstantiationException: Package tsp in both module modifier and module main
在一个模块层内,包明确属于一个模块,两个模块具有相同的包名是错误的。
class 路径上的 classes 不进行此类检查。相反,在您的设置中,包 tsp
已与 main
模块相关联,并且 运行time 甚至没有尝试在 [=67= 中查找 tsp.Agent
] 小路。由于 tsp
包已经与 main
模块相关联,它只在 output/main
位置查找并没有找到 class.
要点是,您必须使用不同的包。当你使用不同的包名时,命令行
java -javaagent:output/modifier.jar --module-path output/main --module main/tsp.App
有效,但代理将加载到未命名模块中。 module-info
将被忽略。但是,您可以通过手动添加来强制将代理加载为模块。这利用了上述行为;当一个模块被添加到环境中时,它的包所有权优先。
当我将您代理的包重命名为 agent
并从我的回答开始使用命令行时,我得到
Exception in thread "main" java.lang.IllegalAccessException: class sun.instrument.InstrumentationImpl (in module java.instrument) cannot access class agent.Agent (in module modifier) because module modifier does not export agent to module java.instrument
at java.base/jdk.internal.reflect.Reflection.newIllegalAccessException(Reflection.java:392)
at java.base/java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:674)
at java.base/java.lang.reflect.Method.invoke(Method.java:560)
at java.instrument/sun.instrument.InstrumentationImpl.loadClassAndStartAgent(InstrumentationImpl.java:491)
at java.instrument/sun.instrument.InstrumentationImpl.loadClassAndCallPremain(InstrumentationImpl.java:503)
这证明代理已作为模块加载。与未命名模块不同,默认情况下它不会导出所有内容。
当我更改 Agent 的 module-info
module modifier {
requires java.instrument;
exports agent to java.instrument;
}
注意我把代理的包重命名为agent
,所以代理主class是agent.Agent
运行成功了。
将代理的 class 更改为
package agent;
import java.lang.instrument.Instrumentation;
public class Agent {
public static void premain(String agentArgs, Instrumentation inst) {
System.out.println("Agent started in " + Agent.class.getModule());
}
}
然后打印
Agent started in module modifier
符合预期。
附带说明一下,您编译模块的方法不必要地复杂。您可以编译一个完整的模块,例如
javac --module-source-path mods -d output -m main
甚至
javac --module-source-path mods -d output -m modifier,main
一次编译。
我正在尝试在模块化应用程序中 运行 一个 javaagent
,但我无法通过命令行使其工作。我创建了尽可能小的存储库:
.
├── Makefile
├── manifest
└── mods
├── main
│ ├── module-info.java
│ └── tsp
│ └── App.java
└── modifier
├── module-info.java
└── tsp
└── Agent.java
mods/main/module-info.java
module main {
}
mods/main/tsp/App.java
package tsp;
public class App {
public static void main(String[] args) {
}
}
mods/modifier/module-info.java
module modifier {
requires java.instrument;
}
mods/modifier/tsp/Agent.java
package tsp;
import java.lang.instrument.Instrumentation;
public class Agent {
public static void premain(String agentArgs, Instrumentation inst) {
}
}
生成文件
SHELL := /bin/bash
.PHONY: clean build_main build_agent run
build_agent: clean
echo -e "Premain-Class: tsp.Agent\nCan-Retransform-Classes: true\n" > manifest
javac --module-path mods/modifier -d output/modifier $$(find mods/modifier -name *.java) && \
jar --create --file output/modifier.jar --manifest manifest -C output/modifier .
build_main: clean
javac --module-path mods/main -d output/main $$(find mods/main -name *.java)
run: build_main build_agent
java -javaagent:output/modifier.jar --module-path output/main --module main/tsp.App
clean:
rm -rf output
manifest
是从 Makefile
自动创建的。
当我执行make run
时,输出是:
rm -rf output
javac --module-path mods/main -d output/main $(find mods/main -name *.java)
echo -e "Premain-Class: tsp.Agent\nCan-Retransform-Classes: true\n" > manifest
javac --module-path mods/modifier -d output/modifier $(find mods/modifier -name *.java) && \
jar --create --file output/modifier.jar --manifest manifest -C output/modifier .
java -javaagent:output/modifier.jar --module-path output/main --module main/tsp.App
Exception in thread "main" java.lang.ClassNotFoundException: tsp.Agent
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:606)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:168)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:522)
at java.instrument/sun.instrument.InstrumentationImpl.loadClassAndStartAgent(InstrumentationImpl.java:431)
at java.instrument/sun.instrument.InstrumentationImpl.loadClassAndCallPremain(InstrumentationImpl.java:525)
*** java.lang.instrument ASSERTION FAILED ***: "result" with message agent load/premain call failed at open/src/java.instrument/share/native/libinstrument/JPLISAgent.c line: 422
FATAL ERROR in native method: processing of -javaagent failed, processJavaStart failed
make: *** [Makefile:14: run] Aborted (core dumped)
相反,当我将 Makefile
中的 run
目标更改为:
run: build_main build_agent
java -javaagent:output/modifier.jar --class-path output/main tsp.App
一切正常。我不想使用像 Gradle 或 Maven 这样的构建工具,因为我想了解为什么它不能从命令行运行。我读过 Loading agent classes and the modules/classes available to the agent class 但老实说,我并不完全清楚。我做了很多尝试,比如使用 --add-modules output/modifier
但没有成功。
> java --version
openjdk 15.0.2 2021-01-19
OpenJDK Runtime Environment (build 15.0.2+7-27)
OpenJDK 64-Bit Server VM (build 15.0.2+7-27, mixed mode, sharing)
代理不能模块化,它们会自动加载到系统 class 加载程序的 class 路径,进入未命名的模块。
我假设 JVM 没有正确涵盖代理是模块化的并且在尝试调用它时在内部崩溃的情况。
你用最近的 Java 11/17 试过这个吗?我认为这可以通过删除代理的模块描述符来解决。
正如其他答案所说,Java 代理被加载到未命名模块中。这隐藏了您的设置问题。当你 运行 你的模块
java -javaagent:output/modifier.jar --add-modules modifier \
--module-path output/main:output/modifier.jar --module main/tsp.App
你会得到
Error occurred during initialization of boot layer
java.lang.LayerInstantiationException: Package tsp in both module modifier and module main
在一个模块层内,包明确属于一个模块,两个模块具有相同的包名是错误的。
class 路径上的 classes 不进行此类检查。相反,在您的设置中,包 tsp
已与 main
模块相关联,并且 运行time 甚至没有尝试在 [=67= 中查找 tsp.Agent
] 小路。由于 tsp
包已经与 main
模块相关联,它只在 output/main
位置查找并没有找到 class.
要点是,您必须使用不同的包。当你使用不同的包名时,命令行
java -javaagent:output/modifier.jar --module-path output/main --module main/tsp.App
有效,但代理将加载到未命名模块中。 module-info
将被忽略。但是,您可以通过手动添加来强制将代理加载为模块。这利用了上述行为;当一个模块被添加到环境中时,它的包所有权优先。
当我将您代理的包重命名为 agent
并从我的回答开始使用命令行时,我得到
Exception in thread "main" java.lang.IllegalAccessException: class sun.instrument.InstrumentationImpl (in module java.instrument) cannot access class agent.Agent (in module modifier) because module modifier does not export agent to module java.instrument
at java.base/jdk.internal.reflect.Reflection.newIllegalAccessException(Reflection.java:392)
at java.base/java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:674)
at java.base/java.lang.reflect.Method.invoke(Method.java:560)
at java.instrument/sun.instrument.InstrumentationImpl.loadClassAndStartAgent(InstrumentationImpl.java:491)
at java.instrument/sun.instrument.InstrumentationImpl.loadClassAndCallPremain(InstrumentationImpl.java:503)
这证明代理已作为模块加载。与未命名模块不同,默认情况下它不会导出所有内容。
当我更改 Agent 的 module-info
module modifier {
requires java.instrument;
exports agent to java.instrument;
}
注意我把代理的包重命名为agent
,所以代理主class是agent.Agent
运行成功了。
将代理的 class 更改为
package agent;
import java.lang.instrument.Instrumentation;
public class Agent {
public static void premain(String agentArgs, Instrumentation inst) {
System.out.println("Agent started in " + Agent.class.getModule());
}
}
然后打印
Agent started in module modifier
符合预期。
附带说明一下,您编译模块的方法不必要地复杂。您可以编译一个完整的模块,例如
javac --module-source-path mods -d output -m main
甚至
javac --module-source-path mods -d output -m modifier,main
一次编译。