在向类添加代码时,遇到运行时异常“类文件太大!”。由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);
}

最佳答案

当类文件的大小太大时(如果您的类文件无论如何都超过2GiB,这将是很奇怪的),而不是当常量池中的项目数超过65534时,不会引发RuntimeException("Class file too large!")

达到此类文件限制仍然是非常不寻常的。因此,我建议重新检查您是否正在使用优化的仪器。当您将ClassReader传递给the ClassWriter’s constructor时,它将复制旧类的整个常量池,如果您仅进行较小的更改,这将是合适的。

但是,如果您要进行重大更改,例如重命名成员或类型,您可能最终会在常量池中获得许多未使用的旧条目,同时添加许多新条目。不将读者传递给作者的构造函数会牺牲性能,但会创建一个仅包含所需条目的新鲜常量池。如果仅靠这还不够,您可以将SKIP_DEBUG传递给ClassReader的构造函数以删除调试信息,从而进一步减少常量池项的数量。

如果仍然不能解决问题,则必须重新设计要注入的内容。对于此类代码,适用与普通手写代码相同的规则。将代码拆分为合理大小的方法,重用通用代码,将这些方法组织在类中。毕竟,如果只注入对现有方法的调用,甚至代码注入本身也会变得更加简单和高效。



问题是您的super.visitLdcInsn(count);指令。这将为每个不同的整数值创建一个新的常量池条目。由于指令号可以是065535之间的任何整数,因此很容易超过可能的常量池条目的数量。

对于该范围内的值,您无需使用常量池条目。有专用于小整数值的字节码指令。但是通常,创建一个实用程序方法为每个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)

09-04 06:32