在向类添加代码时,我遇到了运行时异常“类文件太大了!”由ClassWriter.toByteArray()
触发。
是否有解决此问题的解决方法?例如,如果一个方法太大,我们可以拆分它来解决问题,但是怎么样。
PS:使用asm 5.0.1
ClassReader和ClassWriter的初始化
ClassReader classReader = new ClassReader(in);
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
MyClassVisitor myClassVisitor = new MyClassVisitor(Opcodes.ASM5, classWriter);
classReader.accept(myClassVisitor, ClassReader.SKIP_DEBUG);
对于检测,我只是通过按下堆栈上的指令编号并在每条指令后添加一个调用来记录正在执行的指令(每条指令都是+2)
@Override
public void visitVarInsn(int opcode, int var) {
logInstruction();
super.visitVarInsn(opcode, var);
count++; // instruction number
}
private void logInstruction(){
super.visitLdcInsn(count);
super.visitMethodInsn(Opcodes.INVOKESTATIC, "Logger", "logInstruction",
Type.getMethodDescriptor(Type.VOID_TYPE, Type.INT_TYPE), false);
}
答案 0 :(得分:2)
当类文件太大时,不会抛出RuntimeException("Class file too large!")
(如果你的类文件超过2GiB则会很奇怪),而是当常量池中的项数超过65534时。
达到此类文件限制仍然相当不寻常。因此,我建议您重新检查是否使用了优化的仪器。当您将ClassReader
传递给the ClassWriter
’s constructor时,它将复制旧类的整个常量池,如果您只做了很小的更改,这是合适的。
但是,如果您正在执行重大更改,例如重命名成员或类型,您可能会在常量池中添加大量未使用的旧条目,同时添加大量新条目。不将读者传递给writer的构造函数会牺牲性能,但是创建一个只包含所需条目的新常量池。如果仅此情况不够,您可以将SKIP_DEBUG
传递给ClassReader
的构造函数以删除调试信息,从而进一步减少常量池项的数量。
如果仍然没有帮助,你必须重新设计你注射的任何东西。对于这样的代码,适用与普通手写代码相同的规则。将代码拆分为合理大小的方法,重用公共代码,在类中组织方法。毕竟,如果只注入已有方法的调用,即使代码注入本身也会变得更简单,更有效。
问题是你的super.visitLdcInsn(count);
指令。这将为每个不同的整数值创建一个新的常量池条目。由于指令编号可以是0
和65535
之间的任何整数,因此很容易超过可能的常量池条目数。
对于该范围内的值,您不需要使用常量池条目。存在用于小整数值的专用字节码指令。但通常,创建一个实用程序方法为每个int
值创建最佳指令是值得的:
public final void push(final int value) {
if(value >= -1 && value <= 5) {
super.visitInsn(Opcodes.ICONST_0 + value);
} else if(value == (byte)value) {
super.visitIntInsn(Opcodes.BIPUSH, value);
} else if(value == (short)value) {
super.visitIntInsn(Opcodes.SIPUSH, value);
} else {
super.visitLdcInsn(value);
}
}
然后,通常使用push(intNumber)
代替visitLdcInsn(intNumber)
。