踩了一个 Java 编译时和运行时环境不一致导致的一个坑

2020-10-22 20:18:21 +08:00
 pursuer
这两天测试了一段代码,在 Java11 上正常运行,在 android 上运行报 NoSuchMethodError 异常,异常处就一句

//buf 是一个 ByteBuffer
buf.flip()

报的异常是
java.lang.NoSuchMethodError: No virtual method flip()Ljava.nio.ByteBuffer in class java.nio.ByteBuffer (我凭记忆还原的,可能不完全一样)

通常出现这样的问题的时候我第一反应是使用了 android 不支持的高版本 API 导致的。但翻了下 Java 的 API 文档,发现 flip 函数在有 ByteBuffer 的时候就存在了,然后再看方法签名,注意到报异常的函数签名是 flip()Ljava.nio.ByteBuffer,但查阅的文档中这个函数的签名应该是 flip():Ljava.nio.Buffer,猜测是不是编译的时候选择调用了另一个高版本中存在的方法签名,但是不知道怎么处理。
后来在 StackOverflow 上找到了这个问题的解答。在 Java9 的时候 ByteBuffer 覆写了父类 Buffer 的 flip,mark,reset 等函数,并将返回值改为了 ByteBuffer,导致高版本编译的时候,javac 选择调用了 flip()Ljava.nio.ByteBuffer,在低版本的运行环境中没有这个方法签名,导致出错。解决方法是((Buffer)buf).flip()
感觉 Java 在这块的设计有些奇特,Java 没有返回值重载,但 JVM 实现上却会将不同返回值认定为不同的方法。
3701 次点击
所在节点    Java
6 条回复
pursuer
2020-10-22 21:04:16 +08:00
更正一下,我再次检查文档的时候发现 Java8 的时候 flip 方法是 final 的,但 Java11 后去掉了,导致编译器在高版本下使用了 invokevirtual 指令调用 flip 函数,导致低版本产生异常。上面提到的“JVM 将不同返回值认定为不同方法“这一说法是不成立的。
pursuer
2020-10-22 21:25:27 +08:00
再次更正,我查到 final 关键字对字节码生成没有影响,所以上面说的指令差异也是不对的,那可能还是因为方法签名的问题吧。
SoloCompany
2020-10-22 22:10:19 +08:00
java 兼容性如果都能挑剔的话别的就更不要说了

你说的的这种问题是常见情形, 如果你一定需要使用高版本的 javac 的话, 正确的做法是给 javac 指定 bootstrapclasspath, 使用 JAVA 8 的 rt.jar, 仅仅靠 -source / -target 参数是无法保证 bytecode 能在低版本的 runtime 下运行
abcbuzhiming
2020-10-22 22:20:56 +08:00
java 好像从来没保证说高版本编译的 class 可以在低版本 jvm 上跑啊,我记得 jvm 一直说的是向上兼容,即低版本 jdk 编译的 class 可以跑在高版本 jvm 上(但是从 java9 开始这也不完全保证了,好像只保证 3 个版本之类是兼容的);向下兼容没听说过
pursuer
2020-10-22 22:46:48 +08:00
@SoloCompany
@abcbuzhiming
无奈 Android 的运行时碎片化太厉害,还是会需要使用高版本 android.jar 编译同时要应用兼容低版本系统。
Goooogle
2020-10-22 23:37:05 +08:00
Java 在编译时,会将使用到的方法的签名固化在字节码中的常量池中(类型为 CONSTANT_Methodref_info ),当运行时和编译时的签名不一样时,就会报这个错误。即使是“将参数类型改为其父类型”这种直观看起来可行的方式也不行。
你例子中,ByteBuffer 是 Buffer 的子类型,单纯从语法上讲,把一个方法的 ByteBuffer 参数的类型替换成 Buffer,所有这个方法的调用方都能继续调用,不会有任何问题,但在编译后的方法执行时先去常量池找到对应的符号引用,但该符号引用在运行时环境中没有,不会判断继承关系,而是直接抛出异常。

前段时间刚碰到这个问题。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/717605

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX