我自己写了一个javaagent,用ASM动态检测java类(我没有使用ASM的
COMPUTE_MAXS
或COMPUTE_FRAMES
,我自己手动完成)。实际上,我只是尝试对非构造函数方法使用一个大的 try-catch 块来捕获未捕获的异常或错误并记录此类事件(我的代码实际上是这个问题中代码的修订版本)。
但是,当我在一个开源项目joda-time的测试过程中尝试使用我的javaagent时,出现了以下错误:
org.apache.maven.surefire.testset.TestSetFailedException: org.joda.time.TestAllPackages
at org.apache.maven.surefire.junit.JUnitTestSet.execute(JUnitTestSet.java:116)
at org.apache.maven.surefire.junit.JUnit3Provider.executeTestSet(JUnit3Provider.java:140)
at org.apache.maven.surefire.junit.JUnit3Provider.invoke(JUnit3Provider.java:113)
at org.apache.maven.surefire.booter.ForkedBooter.invokeProviderInSameClassLoader(ForkedBooter.java:379)
at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:340)
at org.apache.maven.surefire.booter.ForkedBooter.execute(ForkedBooter.java:125)
at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:413)
Caused by: java.lang.LinkageError: loader (instance of sun/misc/Launcher$AppClassLoader): attempted duplicate class definition for name: "org/joda/time/DateTimeZone"
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
at java.net.URLClassLoader.defineClass(URLClassLoader.java:468)
at java.net.URLClassLoader.access$100(URLClassLoader.java:74)
at java.net.URLClassLoader$1.run(URLClassLoader.java:369)
at java.net.URLClassLoader$1.run(URLClassLoader.java:363)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:362)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at org.joda.time.TestChronology.<clinit>(TestChronology.java:47)
at org.joda.time.TestAll.suite(TestAll.java:37)
at org.joda.time.TestAllPackages.suite(TestAllPackages.java:36)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.apache.maven.surefire.common.junit3.JUnit3Reflector.createInstanceFromSuiteMethod(JUnit3Reflector.java:157)
at org.apache.maven.surefire.common.junit3.JUnit3Reflector.constructTestObject(JUnit3Reflector.java:124)
at org.apache.maven.surefire.junit.JUnitTestSet.execute(JUnitTestSet.java:75)
... 6 more
据我了解,每次
sun/misc/Launcher$AppClassLoader
尝试加载类时,都会输入 transform
的 ClassFileTransformer
方法。在我修改了类并返回修改后的字节数组后,该类终于被加载了。因此,每个类应该只加载一次。
我进一步尝试在
transform
方法的开头打印出类名和加载它的加载器,我发现org/joda/time/DateTimeZone
只出现一次,这与我的理解一致。
所以现在唯一与我的理解不一致的是错误。为什么与我的经纪人
sun/misc/Launcher$AppClassLoader attempted duplicate class definition
?当我删除我的 -javaagent
选项时,一切都进展顺利。
为了计算帧,ASM 需要在跳转指令的目标处找到多个类的公共超类。为此,ASM 加载类以浏览它们的层次结构。如果您以这种方式加载一个类,而该类在第一次加载期间也已被检测,则该类将在检测后加载,并且最终会出现此错误。
为了避免这种情况,可以重写ASM的ClassWriter的getCommonSuperClass方法。您需要解析传递给该方法的这些类的类文件,而不是加载它们。如果您想要开箱即用的实现,您可以使用 Byte Buddy,它公开 ASM 并以这种方式解析其 ClassWriter。
我在尝试从 IntelliJ IDEA 运行配置时遇到了这个问题 - 使用 Maven 重新加载项目有帮助。