Hotspot JVM如何处理x86上的整数除数溢出



在Java中划分两个int没有什么特别之处。除非处理两种特殊情况之一:

  1. 除以零。(JVMS要求虚拟机抛出ArithmeticException(
  2. 除法溢出(Integer.MIN_VALUE / -1,JVMS要求结果等于Integer.MIN_VALUE((这个问题只针对这种情况(

来自第6章。Java虚拟机指令集。idiv:

有一种特殊情况不满足这一规则:如果被除数是int类型的最大可能大小的负整数,除数为-1,则发生溢出,结果等于被除数。尽管存在溢出,但在这种情况下不会引发异常。

在我的计算机(x86_64(上,本机分区产生SIGFPE错误。

当我编译以下C代码时:

#include <limits.h>
#include <stdio.h>
int divide(int a, int b) {
int r = a / b;
printf("%d / %d = %dn", a, b, a / b);
return r;
}
int main() {
divide(INT_MIN, -1);
return 0;
}

我得到的结果(在x86上(:

tmp $ gcc division.c 
tmp $ ./a.out 
Floating point exception (core dumped)

在ARM(aarch64(上编译的完全相同的代码产生:

-2147483648 / -1 = -2147483648

因此,在x86上,Hotspot虚拟机似乎需要做额外的工作来处理这种情况。

  • 在这种情况下,虚拟机做了什么才能在编译的代码中不损失太多性能
  • 它是否利用了POSIX系统中的信号处理可能性?如果是,它在Windows上使用什么

您是对的-HotSpot JVM不能因为特殊情况而盲目使用idivcpu指令。

因此JVM执行一个额外的检查,即Integer.MIN_VALUE是否除以-1。这种检查既存在于解释器中,也存在于编译后的代码中。

如果我们用-XX:+PrintAssembly检查实际编译的代码,我们会看到类似的东西

0x00007f212cc58410:   cmp    $0x80000000,%eax    ; dividend == Integer.MIN_VALUE?
0x00007f212cc58415:   jne    0x00007f212cc5841f
0x00007f212cc58417:   xor    %edx,%edx
0x00007f212cc58419:   cmp    $0xffffffff,%r11d   ; divisor == -1?
0x00007f212cc5841d:   je     0x00007f212cc58423
0x00007f212cc5841f:   cltd   
0x00007f212cc58420:   idiv   %r11d               ; normal case
0x00007f212cc58423:   mov    %eax,0x70(%rbx)

但是,正如您可能注意到的,除数==0没有检查。这被认为是一种特殊情况,在正常程序中不应该发生这种情况。这被称为隐式异常。JVM记录可能发生这种异常的地方,并依赖于操作系统信号(或Windows术语中的异常(来处理这种情况。

请参阅os_linux_x86.cpp:

if (sig == SIGFPE  &&
(info->si_code == FPE_INTDIV || info->si_code == FPE_FLTDIV)) {
stub =
SharedRuntime::
continuation_for_implicit_exception(thread,
pc,
SharedRuntime::
IMPLICIT_DIVIDE_BY_ZERO);

然而,如果隐式异常经常发生在同一位置,JVM会对编译后的代码进行去优化,然后通过显式零检查重新编译(以避免频繁信号处理的性能损失(。

在这种情况下,虚拟机做了什么才能不损失性能编译后的代码太多?

他们什么都不做。它只是作为if语句实现的。

基于目标体系结构有不同的字节码解释器,但我看了看,所有的实现都是一样的。这是x86

inline jint BytecodeInterpreter::VMintDiv(jint op1, jint op2) {
/* it's possible we could catch this special case implicitly */
if ((juint)op1 == 0x80000000 && op2 == -1) return op1;
else return op1 / op2;
}

我不确定这个评论是在暗示什么。我在JDK邮件列表中找不到任何有趣的关于这个方法的提及,如果我想解释一些历史性的决定,这是我通常会去的。

无论如何,强调"可以"这个词。不管他们的意思是什么,他们都不会这么做。

最新更新