通过检测字节码调用类的静态方法时出现 NoClassDefFoundError
Posted
技术标签:
【中文标题】通过检测字节码调用类的静态方法时出现 NoClassDefFoundError【英文标题】:NoClassDefFoundError when invoking static method of a class by instrumenting byte code 【发布时间】:2017-03-07 23:42:51 【问题描述】:每当分配对象但遇到问题时,我都会尝试调用静态方法。我已将我的代码缩减为较小的工作示例。
MemorySnifferAgent.java
public class MemorySnifferAgent
public static void premain(String agentArguments, Instrumentation instrumentation)
Callback.main(); //Make sure the class is loaded ?
instrumentation.addTransformer(new MemoryAllocationTransformer());
MemoryAllocationTransformer.java
public class MemoryAllocationTransformer implements ClassFileTransformer
public byte[] instrumentByteCode(byte[] bytecode)
ClassReader reader = new ClassReader(bytecode);
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
CustomClassReader customVisitor = new CustomClassReader(Opcodes.ASM4, writer);
reader.accept(customVisitor, 0);
return writer.toByteArray();
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException
byte[] bytes = instrumentByteCode(classfileBuffer);
return bytes;
CustomClassReader.java
public class CustomClassReader extends ClassVisitor
public CustomClassReader(int api, ClassWriter classWriter)
super(api, classWriter);
public MethodVisitor visitMethod(final int access, final String name,
final String desc, final String signature, final String[] exceptions)
MethodVisitor methodWritter = super.visitMethod(access, name, desc, signature, exceptions);
return new CustomMethodWritter(api, name, methodWritter);
CustomMethodWritter.java
class CustomMethodWritter extends MethodVisitor
String name;
public CustomMethodWritter(int i, String name, MethodVisitor methodWriter)
super(i, methodWriter);
this.name = name;
@Override
public void visitTypeInsn(int opcode, String type)
super.visitTypeInsn(opcode, type);
if (opcode == Opcodes.NEW)
super.visitMethodInsn(Opcodes.INVOKESTATIC, "com/chasingnanos/Callback", "onAllocation", "()V", false);
注意:
我意识到利用所有分配需要利用所有 NEW* 操作码和反射 API super.visitMethodInsn(Opcodes.INVOKESTATIC, "com/chasingnanos/Callback", "onAllocation", "()V", false);似乎是问题所在。虽然我无法找到问题所在。抱歉,如果这是一个非常基本的问题。我是字节码操作的新手。
我得到的错误:
java.lang.NoClassDefFoundError - 类:'java/lang/NoClassDefFoundError'
【问题讨论】:
【参考方案1】:我认为这个问题与不同的ClassLoader
s 提供的隔离有关。特别是代理的代码和“主”代码由不同的Classloader
s 加载,因此您不能从“主”代码调用您的Callback
。解决此问题的方法是将您的 Callback
类移动到不同的 jar 并使用 -Xbootclasspath/a
选项将其添加到引导类路径中。
另见类似问题 Java NoClassDefFoundError when calling own class from instrumented method
更新(过滤)
如果您想过滤掉无法访问Callback
的类而不是使用引导程序,您可能应该执行类似的操作,而不是只检查null
:
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException
// check if the class inside the loader can access Callback
// by analyzing its hierarchy of parent class loaders
for (ClassLoader curLoader = loader; ; curLoader = curLoader.getParent())
if (curLoader == null)
System.out.println("Skip '" + className + "' for " + loader);
return null;
else if (curLoader == Callback.class.getClassLoader())
break;
byte[] bytes = instrumentByteCode(classfileBuffer);
return bytes;
【讨论】:
你是对的!!!看起来 premain 是由 sun.misc.Launcher$AppClassLoader@18b4aac2 调用的,它看起来正确,但 transform 是由引导加载程序调用的。我添加了下面的 sn-p 来转换并且一切正常。 if(loader == null) System.out.println("由引导加载程序转换");返回空值; else System.out.println("由" + loader.toString() + " loader");关于为什么在 AppClassLoader 之后调用引导加载程序的任何想法? 我不确定我是否明白你的问题。回答“因为所有类都是延迟加载(按需)”让你满意吗?我还想警告您,在执行自己的基于ClassLoader
的魔术的复杂应用程序中(例如 web/app 容器),仅检查 loader == null
可能还不够
我知道这些类是延迟加载的,但我不确定是否调用了引导程序。我认为它在第一次调用 AppClassLoader 之前只调用了一次。
您使用单词“bootstrap”作为名词,在这种情况下对我来说没有意义。有一个“引导类路径”和一个“引导类加载器”,其中“引导”是一个形容词。实际上,“引导类加载器”会加载一大堆类。看看String.class.getClassLoader()
。哪个加载器将加载类不是由加载类的时刻定义的,而是由它在类路径中的位置定义的。实际上整个rt.jar
默认情况下在引导类路径中(请参阅docs.oracle.com/javase/8/docs/technotes/tools/…),如果您更改它,您将破坏许可证
必须强调的是,如果没有过滤,这个转换器将被所有随后加载的类调用,包括它自己使用的类,例如的 ASM 库。该错误消息表明,即使报告失败,即加载 NoClassDefFoundError
本身,也失败了。通常,对每个分配执行操作而不进行过滤是自找麻烦。如果 com.chasingnanos.Callback
类或其依赖项之一尚未加载,它们也会被检测,因此如果 onAllocation()
执行任何分配,您将获得无限递归。以上是关于通过检测字节码调用类的静态方法时出现 NoClassDefFoundError的主要内容,如果未能解决你的问题,请参考以下文章
为啥在 Eloquent 模型中调用方法时出现“不应静态调用非静态方法”?
Java 字节码检测:对 defineClass 的反射调用中的 NullPointerException