asm class 文件太大
asm class file too large
在向 class 添加代码时,我遇到了由 ClassWriter.toByteArray()
触发的运行时异常 "Class File too large!"。
是否有解决此问题的解决方法?比如一个方法太大了,我们可以拆分来解决问题,但是class.
呢?
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);
}
当 class 文件太大时不会抛出 RuntimeException("Class file too large!")
(如果你的 class 文件超过 2GiB 就真的很奇怪),而是当常量池中的项数超过 65534.
遇到这个 class 文件限制仍然很不寻常。所以我建议重新检查您是否正在使用优化的仪器。当您将 ClassReader
传递给 the ClassWriter
’s constructor 时,它会复制旧 class 的整个常量池,如果您只进行较小的更改,这是合适的。
但是,如果您正在进行重大更改,例如重命名成员或类型时,您可能会在添加大量新条目的同时在常量池中得到大量未使用的旧条目。不将 reader 传递给编写器的构造函数会牺牲性能,但会创建一个仅包含所需条目的新常量池。如果这还不够,您可以将 SKIP_DEBUG
传递给 ClassReader
的构造函数以删除调试信息,进一步减少常量池项的数量。
如果仍然没有帮助,则必须重新设计注入的内容。对于此类代码,适用与普通手写代码相同的规则。将代码拆分为大小合理的方法,重用公共代码,将方法组织在 classes 中。毕竟,如果您只是注入对现有方法的调用,即使是代码注入本身也会变得更简单、更高效。
问题出在您的 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)
。
在向 class 添加代码时,我遇到了由 ClassWriter.toByteArray()
触发的运行时异常 "Class File too large!"。
是否有解决此问题的解决方法?比如一个方法太大了,我们可以拆分来解决问题,但是class.
呢?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);
}
当 class 文件太大时不会抛出 RuntimeException("Class file too large!")
(如果你的 class 文件超过 2GiB 就真的很奇怪),而是当常量池中的项数超过 65534.
遇到这个 class 文件限制仍然很不寻常。所以我建议重新检查您是否正在使用优化的仪器。当您将 ClassReader
传递给 the ClassWriter
’s constructor 时,它会复制旧 class 的整个常量池,如果您只进行较小的更改,这是合适的。
但是,如果您正在进行重大更改,例如重命名成员或类型时,您可能会在添加大量新条目的同时在常量池中得到大量未使用的旧条目。不将 reader 传递给编写器的构造函数会牺牲性能,但会创建一个仅包含所需条目的新常量池。如果这还不够,您可以将 SKIP_DEBUG
传递给 ClassReader
的构造函数以删除调试信息,进一步减少常量池项的数量。
如果仍然没有帮助,则必须重新设计注入的内容。对于此类代码,适用与普通手写代码相同的规则。将代码拆分为大小合理的方法,重用公共代码,将方法组织在 classes 中。毕竟,如果您只是注入对现有方法的调用,即使是代码注入本身也会变得更简单、更高效。
问题出在您的 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)
。