这个问题纠结了我比较久的时间,由于那个项目用到了ASM8,一开始怀疑是ASM8 对该类做了不当修改,并且花了一些时间来了解 ASM,最终当我分析了字节码,并复现了该问题,我才最终确定了原因。
当我们分别使用 JDK8, JDK9 来编译,产生的字节码比较如下(左边是JDK9的编译结果,右边是JDK8的编译结果)
可以看出,两者的字节码版本都是52.0(即 JDK8),但是 rewind这个方法,在字节码级别上,二者编译结果不同,JDK9上。方法的描述符为 ()Ljava/nio/ByteBuffer;
由于虚拟机最终是根据字节码来进行方法查找的,而 java/nio/ByteBuffer.rewind:()Ljava/nio/ByteBuffer; 意味着,它会去 java/nio/ByteBuffer 类(而不是其父类)中寻找 rewind 方法,且该方法的参数列表为空,返回类型为 Ljava/nio/ByteBuffer
(关于JVM是如何查找和指向一个方法的,参考 https://blog.csdn.net/aha_jasper/article/details/105646684 )
但是自从JDK9以后,由于 ByteBuffer 重写了 rewind 方法,且重写后返回类型为 ByteBuffer(而不是 Buffer),因此导致了这个问题。
@Override public ByteBuffer rewind() { super.rewind(); return this; } 如何解决该问题由于代码本身就能在 JDK8 上通过编译,因此只需要在找到原因后,对代码进行兼容性优化,就可以解决该问题,针对本文的例子,和那个项目的具体情况,我最终做了下面这样的修改来解决问题
Buffer buffer = ByteBuffer.allocateDirect(14); ((ByteBuffer) buffer).rewind(); 结论:即使代码同时能在不同版本的JDK上通过编译,也需要尽量避免在高版本JDK编译后拿到低版本JDK运行;
如果一定要这样做,则需要进行充分测试,并对代码进行兼容性改造。