在 JNI DefineClass 中解决依赖关系

Resolving Dependencies in JNI DefineClass

我正在使用 JVMTI 编写应用程序。我正在尝试检测字节码:通过在每个方法条目上注入方法调用。

我知道该怎么做,但问题出在仪器 class 中,比如说它叫做 Proxy,我使用 JNI 函数 DefineClass 加载它。我的 Proxy 在 Java Class 库中有一些依赖项,目前只有 java.lang.ThreadLocal<Boolean>.

现在,假设我有这个,其中 inInstrumentMethod 是普通的 boolean:

public static void onEntry(int methodID)
{
    if (inInstrumentMethod) {
        return;
    } else {
        inInstrumentMethod = true;
    }

    System.out.println("Method ID: " + methodID);

    inInstrumentMethod = false;
}

代码编译并运行。但是,如果我将 inInstrumentMethod 设置为 java.lang.ThreadLocal<Boolean>,我将得到一个 NoClassDefFoundError。代码:

private static ThreadLocal<Boolean> inInstrumentMethod = new ThreadLocal<Boolean>() {
        @Override protected Boolean initialValue() {
            return Boolean.FALSE;
        }
    };

public static void onEntry(int methodID)
{
    if (inInstrumentMethod.get()) {
        return;
    } else {
        inInstrumentMethod.set(true);
    }

    System.out.println("Method ID: " + methodID);

    inInstrumentMethod.set(false);
}

我的猜测是依赖关系没有正确解析,java.lang.ThreadLocal没有加载(因此找不到)。那么问题是,我如何强制 Java 加载 java.lang.ThreadLocal?我不认为我可以在这种情况下使用 DefineClass;有替代方案吗?

我不认为解决标准 class java.lang.ThreadLocal 有问题,而是内部 class 扩展它,由

生成
new ThreadLocal<Boolean>() {
    @Override protected Boolean initialValue() {
        return Boolean.FALSE;
    }
};

由于内部和外部 class 之间的循环依赖,通过 DefineClass 解决这个问题可能确实是不可能的,所以没有允许定义它们的顺序,除非你有一个完整的ClassLoader returns class 按需提供。

最简单的解决方案是完全避免生成内部 class,这可以通过 Java 8:

private static ThreadLocal<Boolean> inInstrumentMethod
                                  = ThreadLocal.withInitial(() -> Boolean.FALSE);

如果您使用 Java 8 之前的版本,则不能那样使用,因此在这种情况下最好的解决方案是重写代码以接受默认值 null 作为初始值,无需指定不同的初始值:

private static ThreadLocal<Boolean> inInstrumentMethod = new ThreadLocal<>();

public static void onEntry(int methodID)
{
    if (inInstrumentMethod.get()!=null) {
        return;
    } else {
        inInstrumentMethod.set(true);
    }

    System.out.println("Method ID: " + methodID);

    inInstrumentMethod.set(null);
}

您还可以将匿名内部 class 转换为顶级 class。从那时起,class 不再依赖于它以前的外部 class,首先定义 ThreadLocal 的子类型,然后再定义 class 使用它,应该可以解决问题.