相同的 Java SerializedLambda 对 implMethodKind 返回不同的结果

问题描述 投票:0回答:2

我是罗纳德,JobRunr 的作者。 JobRunr 是一个后台作业调度库,它使用 SerializedLambda 和 ASM 来分析 Java 8 lambda 并将其转换为后台作业。

最近报告了一个错误,我尝试在 JobRunr 中重现它,以便我可以编写一个测试来防止回归。

有趣的是,在同一个Java版本(17.0.2)上,即使我复制了确切的代码,我也无法重现它。

此项目中,生成的

SerializedLambda
implMethodKind
等于 5 (
REF_invokeVirtual
)。

然而,在 JobRunr 本身中, generated

SerializedLambda
implMethodKind
等于 7 (
REF_invokeSpecial
)。

生成

SerializedLambda
的实际代码如下:

public class GeoService {
    Logger LOG = LoggerFactory.getLogger(GeoService.class);

    public void executeGeoTreeJob(JobContext jobContext, long geoNameId, UserId userId) {
        LOG.error("Running: " + geoNameId);
    }

    public void run() {
        LOG.error("Starting job");
        UserId userId = new UserId();
        userId.setValue("test");
        long geoNameId = 1234;

        JobLambda jobLambda = () -> executeGeoTreeJob(JobContext.Null, geoNameId, userId);

        SerializedLambda serializedLambda = SerializedLambdaConverter.toSerializedLambda(jobLambda);
        System.out.println("=======");
        System.out.println("serializedLambda " + serializedLambda.getImplMethodKind());
        System.out.println("=======");

        BackgroundJob.enqueue(() -> executeGeoTreeJob(JobContext.Null, geoNameId, userId));
    }
}

此项目中,生成的

SerializedLambda
implMethodKind
等于 5 (
REF_invokeVirtual
)。

然而,在 JobRunr 本身中, generated

SerializedLambda
implMethodKind
等于 7 (
REF_invokeSpecial
)。

为什么我会得到不同的

implMethodKind
值?或者,换句话说,我需要对设置/JVM/...执行哪些操作才能获得与示例项目中相同的结果。

更新:

我创建

SerializedLambda
如下:

public class SerializedLambdaConverter {

    private SerializedLambdaConverter() {

    }

    public static <T> SerializedLambda toSerializedLambda(T value) {
        if (!value.getClass().isSynthetic()) {
            throw new IllegalArgumentException("Please provide a lambda expression (e.g. BackgroundJob.enqueue(() -> myService.doWork()) instead of an actual implementation.");
        }

        if (!(value instanceof Serializable)) {
            throw new JobRunrException("The lambda you provided is not Serializable. Please make sure your functional interface is Serializable or use the JobLambda interface instead.");
        }

        try {
            Method writeReplaceMethod = value.getClass().getDeclaredMethod("writeReplace");
            makeAccessible(writeReplaceMethod);
            return (SerializedLambda) writeReplaceMethod.invoke(value);
        } catch (Exception shouldNotHappen) {
            throw shouldNotHappenException(shouldNotHappen);
        }
    }
}
java serialization lambda runtime bytecode
2个回答
0
投票

SerializedLambda
反映了 lambda 表达式是如何编译的,正如这个答案中所解释的,它依赖于编译器。因此,结果不依赖于 Java 运行时版本,而是依赖于包含 lambda 表达式的类所使用的编译器,并且没有运行时选项来更改结果。

除了选择将主体编译为实例方法或接收

static
作为参数的
this
方法之外,编译器还可以自由地将私有实例方法的调用编码为
invokespecial
invokevirtual
行为。两者同等有效。

我们可以使用以下程序来自检编码的调用:

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.spi.ToolProvider;

public class LambdaBinary {
    public static void main(String[] args) {
        ToolProvider.findFirst("javap").ifPresent(new LambdaBinary()::print);
    }
  
    private void print(ToolProvider tp) {
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        tp.run(pw, pw, "-v", LambdaBinary.class.getName());
        StringBuffer b = sw.getBuffer();
        System.out.append(b,
            b.lastIndexOf("BootstrapMethods:"),
            b.lastIndexOf("InnerClasses:"));
    }
}

当使用

javac
版本8到14或使用Eclipse的编译器进行编译时,结果将类似于

BootstrapMethods:
  0: #85 REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
    Method arguments:
      #92 (Ljava/lang/Object;)V
      #94 REF_invokeSpecial LambdaBinary.print:(Ljava/util/spi/ToolProvider;)V
      #97 (Ljava/util/spi/ToolProvider;)V

当使用 JDK 14 或更高版本的

javac
进行编译时,它会打印类似

的内容
BootstrapMethods:
  0: #85 REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
    Method arguments:
      #92 (Ljava/lang/Object;)V
      #94 REF_invokeVirtual LambdaBinary.print:(Ljava/util/spi/ToolProvider;)V
      #97 (Ljava/util/spi/ToolProvider;)V

如上所述,

REF_invokeSpecial
REF_invokeVirtual
之间的差异由
SerializedLambda
反映,您唯一能做的就是调整代码以相同的方式处理这两个值。


-1
投票

只是为了检查一下,您可以尝试修改您的

.idea/compiler.xml
,这部分:

   <bytecodeTargetLevel target="17">
      <module name="JobRunr.tests.e2e-elasticsearch-gson.test" target="11" />
      <module name="JobRunr.tests.e2e-elasticsearch-jackson.test" target="11" />
      <module name="JobRunr.tests.e2e-json-gson.test" target="11" />
      <module name="JobRunr.tests.e2e-mariadb-gson.test" target="11" />
      <module name="JobRunr.tests.e2e-mariadb-jackson.test" target="11" />
      <module name="JobRunr.tests.e2e-mongo-gson.test" target="11" />
      <module name="JobRunr.tests.e2e-mongo-jackson.test" target="11" />
      <module name="JobRunr.tests.e2e-mysql-gson.test" target="11" />
      <module name="JobRunr.tests.e2e-mysql-jackson.test" target="11" />
      <module name="JobRunr.tests.e2e-oracle-gson.test" target="11" />
      <module name="JobRunr.tests.e2e-oracle-jackson.test" target="11" />
      <module name="JobRunr.tests.e2e-postgres-gson.test" target="11" />
      <module name="JobRunr.tests.e2e-postgres-jackson.test" target="11" />
      <module name="JobRunr.tests.e2e-redis-gson.test" target="11" />
      <module name="JobRunr.tests.e2e-redis-jackson.test" target="11" />
      <module name="JobRunr.tests.e2e-sqlserver-gson.test" target="11" />
      <module name="JobRunr.tests.e2e-sqlserver-jackson.test" target="11" />
      <module name="JobRunr.tests.e2e-ui.main" target="11" />
      <module name="JobRunr.tests.e2e-ui.test" target="11" />
      <module name="JobRunr.tests.e2e-vm-jdk.test" target="11" /> <----- here
    </bytecodeTargetLevel>
© www.soinside.com 2019 - 2024. All rights reserved.