Java内在方法和本地方法有什么区别?

15

Java内置函数在许多地方(例如这里)提到。我的理解是这些函数是使用特殊的本机代码处理的方法。这似乎类似于JNI方法,它也是本机代码块。

两者有什么区别呢?


1
我认为区别在于JNI调用时,你的Java类知道它正在调用本地方法。而对于内置函数,JVM只是决定透明地替换掉方法(该方法以字节码形式存在于类中),并使用自己的实现。如果你使用另一个没有这种内置函数的JVM,你将得到“正常”的实现。 - Thilo
内在方法是一种本机方法,与 JRE API 中的方法完全对应。 - user207421
3个回答

15
JIT知道内置函数,所以它可以将相关的机器指令内联到正在JIT编译的代码中,并在热循环的一部分进行优化。
JNI函数对于编译器来说是一个100%的黑匣子,具有显着的调用/返回开销(特别是如果您只使用它作为标量)。
即使它只是一个调用类似于int bitcount(unsigned x){ return __builtin_popcount(x); }的函数的语句,该语句编译为x86-64 popcnt eax, edi ; ret(x86-64系统V调用约定),调用者(JIT编译器正在发出的)仍然必须假设所有被调用破坏的寄存器都已被破坏。 在x86-64上,这是大多数整数寄存器和所有FP /向量寄存器。 (与调用黑匣子函数和内置函数相比,静态C ++编译器的成本相同)。但我怀疑调用JNI函数的成本还包括额外的开销。
当然,对任何未知函数的调用意味着寄存器中的变量可能需要与内存同步,如果JIT编译器无法证明没有其他引用它们,则需要进行逃逸分析。
此外,内置函数意味着JVM“理解”函数的作用,并可以通过其进行优化。例如,对于常量传播,它知道popcount(5)= 2个设置位。但是对于实际的JNI函数,它仍然必须调用它。每次调用都是可见的副作用,除非有一种方法声明该函数为“纯净”的,以便可以进行CSE
通过大量内联,编译时常量并不罕见。

CSE 是什么意思? - Danilo Piazzalunga
1
@DaniloPiazzalunga: https://en.wikipedia.org/wiki/Common_subexpression_elimination - Peter Cordes

14

主要的区别在于JVM知道内置方法的实现,可以用机器相关的优化指令替换原始的Java代码(有时甚至只需单个处理器指令);而JNI方法的实现对JVM来说是未知的。

这样会带来一些限制,如无法对JNI方法应用某些优化技术,需要在调用堆栈上做额外工作等。

PS:您提供的链接包含了特定JVM已知方法的列表。此列表可能因JVM而异。


11
一个“native”方法是一个广义的术语,意味着该方法在JVM本身或动态加载的本地库中实现。 一个“native”方法是在类的Java源代码中声明为“native”的方法。 一个“intrinsic”方法是JVM运行时(特别是JIT编译器)执行特殊优化的方法。其中,“intrinsic”之一的含义是调用序列不是JNI调用。但是优化可能更加广泛。 请注意,“native”和“intrinsic”是正交的: - 一个方法既可以是“native”又可以是“intrinsic”,例如arraycopy。通常情况下,即同时是“native”又是“intrinsic”的方法将不作为JNI方法实现。 - 一个方法可以是“intrinsic”,而没有成为“native”,例如某些Java版本中的一些String方法。在这种情况下,Java源代码及其字节码在JIT编译版本的方法中被忽略。
这似乎与JNI方法相似,它也是一块本地代码的块。 JNI是用于实现非“intrinsic”的“native”方法的API。因此,JNI方法是使用与JNI调用序列兼容的签名在C / C++中实现的方法。

问题在于JNI方法调用序列比典型的Java-to-Java或Java-to-intrinsic调用序列更加复杂。这是因为JNI调用的普适性,以及需要检查和映射Java对应C/C++类型之间的参数/结果等方面造成的。

JNI方法与Java和intrinsic方法相比的另一个问题是JIT编译器完全不知道JNI方法实际上正在做什么,因此无法在调用边界上应用各种优化,例如内联。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接