Java中的本地实现是什么?

35

如果我们查看Java Object类,我们可以找到一些方法,例如:

public native int hashCode()
protected native Object clone()

这些本地方法是什么,这些方法如何工作?

5个回答

37
这些方法要么是内在的,要么是在Java之外以“本机”代码编写的,即特定于给定的机器。
你提到的那些方法是内在的并且是JDK的一部分,但是你也可以使用Java Native Interface (JNI)编写自己的本机方法。通常会使用C来编写这些方法,但许多其他语言(如Python)也可以轻松地使用这种方式编写方法。以这种方式编写代码是为了性能,或者因为它需要访问平台特定的基础设施,这不能在纯Java中完成。
hashcode()的情况下,这是由JVM实现的。这是因为哈希码通常与JVM所知道的某些内容相关。在早期的JVM上,这与对象在内存中的位置有关 - 在其他JVM上,对象可能会移动到内存中,因此可能会使用更复杂(但仍然非常快速)的方案。

6
JVM实现了hashcode()方法,为什么它需要是native的?这里的intrinsic具体指什么意思?如果hashcode()方法由JVM实现,为了提高其性能,可以将其声明为本地(native)方法。在Java中,本地方法是使用非Java语言(如C或C++)编写的方法,可以通过Java Native Interface(JNI)调用。而"intrinsic"则指在JVM内部自带的一些方法,例如Object类的hashCode()方法就是一个intrinsic方法,因为它可以直接由JVM处理而无需使用JNI调用外部代码。 - Geek
他所说的内在意味着是JDK的一部分。您不会为本机调用提供自己的(外在的)实现。我认为这个术语并不标准,但他的意思非常清楚。在这个例子中,hashcode()是本地的,正如发布者解释的那样,因此可以使用一些对JVM(用C/C++编写)内部的功能。如果您在Java中编写了hashcode(),您将无法仅仅哈希其地址;Java没有地址!使用本地允许对代码的这个常见部分进行高效和实用的解决方案(以读者理解为代价)。 - user2183336

20

大多数本地方法都是使用JNI实现的,就像其他答案中提到的那样。

然而,诸如Object.hashCode之类的性能关键方法通常被实现为内置函数。当字节码编译成机器码时,Java编译器会识别方法调用并直接内联适当的代码。这显然比通过JNI调用一个微不足道的方法要快得多。

许多人声称Object.hashCode将返回内存中对象表示的地址。在现代实现中,对象实际上会在内存中移动。相反,对象头的一部分用于存储该值,该值可能在第一次请求该值时从内存地址懒洋洋地推导出来。


2
请访问以下链接以获取更多详细信息:https://dev59.com/r2Yr5IYBdhLWcg3wNHqh#13860488 - Damian Leszczyński - Vash

11

这些本地方法是什么?它们是如何工作的?

以下是最小示例,以便更清楚:

Main.java:

public class Main {
    public native int square(int i);
    public static void main(String[] args) {
        System.loadLibrary("Main");
        System.out.println(new Main().square(2));
    }
}

Main.c:

->

Main.c:

#include <jni.h>
#include "Main.h"

JNIEXPORT jint JNICALL Java_Main_square(
    JNIEnv *env, jobject obj, jint i) {
  return i * i;
}

编译并运行:

sudo apt-get install build-essential openjdk-7-jdk
export JAVA_HOME='/usr/lib/jvm/java-7-openjdk-amd64'
javac Main.java
javah -jni Main
gcc -shared -fpic -o libMain.so -I${JAVA_HOME}/include \
  -I${JAVA_HOME}/include/linux Main.c
java -Djava.library.path=. Main

输出:

4

在Ubuntu 14.04上测试通过。同时也适用于Oracle JDK 1.8.0_45。

有一个GitHub示例供您使用。

解释:

它允许您:

  • 从Java中以任意汇编代码调用已编译的动态加载库(此处使用C编写)
  • 将结果返回到Java中

这可以用于:

  • 在临界区编写更快的代码,使用更好的CPU汇编指令(不是CPU可移植的)
  • 进行直接系统调用(不是操作系统可移植的)

代价是较低的可移植性。

您还可以从C中调用Java,但必须首先在C中创建JVM:如何从C++调用Java函数?

以OpenJDK 8为例

让我们找出在jdk8u60-b27中定义了Object#clone的位置。

首先我们找到:

find . -name Object.java

这导致我们来到jdk/src/share/classes/java/lang/Object.java#l212

protected native Object clone() throws CloneNotSupportedException;

现在来到了最困难的部分,找出所有间接引用中clone所在的位置。帮助我的查询语句是:

find . -iname object.c

这将找到实现Object本地方法的C或C++文件。它将我们引导到jdk/share/native/java/lang/Object.c#l47:

static JNINativeMethod methods[] = {
    ...
    {"clone",       "()Ljava/lang/Object;",   (void *)&JVM_Clone},
};

JNIEXPORT void JNICALL
Java_java_lang_Object_registerNatives(JNIEnv *env, jclass cls)
{
    (*env)->RegisterNatives(env, cls,
                            methods, sizeof(methods)/sizeof(methods[0]));
}

这将引导我们到JVM_Clone符号:

grep -R JVM_Clone
这导致我们跳转到 hotspot/src/share/vm/prims/jvm.cpp#l580
JVM_ENTRY(jobject, JVM_Clone(JNIEnv* env, jobject handle))
    JVMWrapper("JVM_Clone");
在扩展了一堆宏之后,我们得出结论,这是定义点。

11
原生方法大多数是用C实现的,并编译成本地代码直接在计算机上运行。这与普通方法不同,普通方法是用Java实现的,并编译成Java字节码,由Java虚拟机(JVM)执行。要从Java接口到这些方法,您需要使用Java Native Interface (JNI)。原生代码主要用于访问低级别的东西。对于hashCode,这是内存中对象的地址。我猜测clone的其他用途是将给定对象的原始内存复制到克隆对象中。使用原生代码的缺点是您会失去JVM的安全性和安全性,即由于原生代码中的错误,您的程序可能会崩溃或存在安全漏洞。

0
Java中的本地方法是使用称为JNI(Java Native Interface)的 'Java本机接口' 实现的。

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