You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用ASM的MethodVisitor统计常量访问次数及问题排查

统计类中Static Final常量使用次数的问题解析与解决方案

问题原因

  1. 编译期常量的折叠优化
    对于static final的编译期常量(比如基本类型、字符串,且值在编译阶段就能确定),javac会执行常量折叠优化:直接将常量的值嵌入到代码使用的位置,而不是生成GETSTATIC指令去访问字段。这就导致visitFieldInsn方法根本不会被触发,因为字节码里已经没有访问该字段的指令了。
  2. 常量加载指令的多样性
    常量折叠后,不同类型的常量会对应不同的字节码加载指令:
    • 小范围整数用ICONST_*/BIPUSH/SIPUSH
    • 字符串、包装类实例用LDC
    • 浮点数用FCONST_*/DCONST_*
      如果只监听visitLdcInsn,会漏掉其他类型常量的加载指令,自然无法统计到所有常量的使用。

解决方案

要完整统计static final常量的使用,需要分两步处理:先收集类中所有static final常量的元数据,再在方法中匹配所有可能加载常量值的字节码指令,最终映射到对应的常量字段名统计次数。

步骤1:收集类中Static Final常量的元数据

在ClassVisitor的visitField方法中,筛选出static final字段,记录字段名和对应的值(编译期常量会直接携带value参数),同时标记运行期初始化的static final字段(这类字段会通过GETSTATIC访问)。

// 存储编译期static final常量的字段名->值映射
private Map<String, Object> fieldNameToValue = new HashMap<>();
// 存储运行期初始化的static final字段名
private Set<String> runtimeStaticFinalFields = new HashSet<>();

@Override
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
    boolean isStaticFinal = (access & Opcodes.ACC_STATIC) != 0 && (access & Opcodes.ACC_FINAL) != 0;
    if (isStaticFinal) {
        if (value != null) {
            // 编译期常量,直接记录值
            fieldNameToValue.put(name, value);
        } else {
            // 运行期初始化的static final字段,后续监听GETSTATIC指令
            runtimeStaticFinalFields.add(name);
        }
    }
    return super.visitField(access, name, desc, signature, value);
}

步骤2:在MethodVisitor中匹配常量加载指令

需要覆盖MethodVisitor中所有可能加载常量的方法,将指令中的值与之前收集的常量元数据匹配,更新constantFrequencyMap的计数:

private Map<String, Integer> constantFrequencyMap = new HashMap<>();

@Override
public void visitFieldInsn(int opcode, String owner, String name, String desc) {
    // 处理运行期static final常量的GETSTATIC访问
    if (opcode == Opcodes.GETSTATIC && runtimeStaticFinalFields.contains(name)) {
        constantFrequencyMap.put(name, constantFrequencyMap.getOrDefault(name, 0) + 1);
    }
    super.visitFieldInsn(opcode, owner, name, desc);
}

@Override
public void visitLdcInsn(Object cst) {
    matchConstantValue(cst);
    super.visitLdcInsn(cst);
}

// 处理整数常量加载指令
@Override
public void visitIconst_0() { matchConstantValue(0); super.visitIconst_0(); }
@Override
public void visitIconst_1() { matchConstantValue(1); super.visitIconst_1(); }
@Override
public void visitIconst_2() { matchConstantValue(2); super.visitIconst_2(); }
@Override
public void visitIconst_3() { matchConstantValue(3); super.visitIconst_3(); }
@Override
public void visitIconst_4() { matchConstantValue(4); super.visitIconst_4(); }
@Override
public void visitIconst_5() { matchConstantValue(5); super.visitIconst_5(); }
@Override
public void visitIconst_m1() { matchConstantValue(-1); super.visitIconst_m1(); }

@Override
public void visitBIPUSH(int value) { matchConstantValue(value); super.visitBIPUSH(value); }
@Override
public void visitSIPUSH(int value) { matchConstantValue(value); super.visitSIPUSH(value); }

// 处理浮点数常量加载指令
@Override
public void visitFconst_0() { matchConstantValue(0.0f); super.visitFconst_0(); }
@Override
public void visitFconst_1() { matchConstantValue(1.0f); super.visitFconst_1(); }
@Override
public void visitFconst_2() { matchConstantValue(2.0f); super.visitFconst_2(); }

@Override
public void visitDconst_0() { matchConstantValue(0.0d); super.visitDconst_0(); }
@Override
public void visitDconst_1() { matchConstantValue(1.0d); super.visitDconst_1(); }

// 匹配常量值到对应的字段名,更新计数
private void matchConstantValue(Object targetValue) {
    for (Map.Entry<String, Object> entry : fieldNameToValue.entrySet()) {
        if (targetValue.equals(entry.getValue())) {
            String fieldName = entry.getKey();
            constantFrequencyMap.put(fieldName, constantFrequencyMap.getOrDefault(fieldName, 0) + 1);
            // 如果存在值重复的常量,这里可以结合字段类型进一步区分
            break;
        }
    }
}

注意事项

  • 值冲突问题:如果两个不同的static final常量值相同(比如两个不同的字符串常量值一样),仅通过值匹配会统计错误。这种情况需要结合字段的类型描述符(desc)来做更精确的匹配,比如在收集元数据时同时存储字段的类型,匹配时先校验类型再比较值。
  • 运行期常量的特殊性:对于在静态代码块中初始化的static final字段,字节码会生成GETSTATIC指令,因此可以通过visitFieldInsn直接捕获统计。

内容的提问来源于stack exchange,提问作者Zihe Ji

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 14:37:30