jdk 中有很多 native 方法,比如 Object 类的 registerNatives 方法、String 类的 intern 方法等。这些方法在 java 层面只有接口定义,具体的方法实现则是在 jdk 中,采用 c/c++ 实现。本文主要讲下如何找到 native 方法的实现。
查找的思路
在 java 中,为了实现特定的功能,有时需要调用非 java 代码实现的函数(比如 c/c++ 函数),JNI(Java Native Interface)提供了这个能力。JNI 规定在 java 代码中使用 native 关键字声明方法,按照一定的规范提供 c/c++ 等外部实现,然后就可以将外部依赖加载到 JVM 中并调用对应方法。
对于自定义的 native 方法,对应的实现位于自定义的类库中。而对于 jdk 中的 native 方法,对应的实现则位于 jdk 源码中,要查看方法实现,首先需要下载 jdk 源码。本文使用的是 openjdk 8 的源码,可以从 https://github.com/gorden5566/jdk8u_jdk.git 下载,如果速度慢也可以从 https://gitee.com/gorden5566/jdk8u_jdk.git 下载。
有了源码后,下一个问题就是如何找到对应的实现。因为 jdk 中代码非常多,不可能去一个文件一个文件地查看,我们需要有更好的搜索方法。在 JNI入门之详细介绍 有提到,native 方法的本地方法名是遵循一定的规则生成的。因此可以先生成对应的本地方法名,然后再到源码中搜索。
生成本地方法名
以 String 类的 intern 方法为例。String 类的源码如下
1 | package java.lang; |
注意,为了方便查看,这里删掉了不相关的代码。
按照生成规则生成本地方法名
根据 JNI 的本地方法名生成规范:
- 前缀为
Java_
- 完全限定的类名(包括包名和类的全路径),中间以
_
分割 - 方法名
- 对于重载的 native 方法,方法名后要再跟上
__
和参数标签
我们可以推断出 intern 方法的本地方法名:
- 以
Java_
开头 - 包名转换后为
java_lang_String
- 方法名为
intern
拼接后结果为 Java_java_lang_String_intern
使用工具生成
自己按照规则拼写本地方法名容易出错,一个更简单的方法是通过工具生成。在 JNI入门之HelloWorld 里,使用了 javah
命令,根据 class 文件自动生成本地方法的头文件。
同样,我们也可以使用 javah
命令生成 String
类的头文件。这里有个背景知识是:java 类加载遵循双亲委派机制,它确保了基础的类不会被用户自定义类覆盖。因此,我们只需要定义一个空的 String
类,确保包路径与 jdk 中的 String
类的路径一致,然后就可以生成所需的头文件。
String
类代码如下:
1 | package java.lang; |
- 执行
javac String.java -d .
生成 class 文件 - 执行
javah java.lang.String
生成头文件
相关文件如下:
1 | . |
打开 java_lang_String.h
1 | /* DO NOT EDIT THIS FILE - it is machine generated */ |
其中 Java_java_lang_String_intern
正是生成的本地方法名。
搜索源码
下一步就是在 jdk 源码中搜索关键字,推荐使用 grep
命令,简洁高效。
切换到前面下载的 jdk 源码目录下,执行如下命令进行搜索
1 | grep -nr "Java_java_lang_String_intern" . |
不出意外你会看到如下信息:
1 | ./src/share/native/java/lang/String.c:30:Java_java_lang_String_intern(JNIEnv *env, jobject this) |
这正是我们要找的 intern 方法的实现,内容如下:
1 |
|
这只是一个入口,它的实现与虚拟机相关,因此需要到 HotSpot 目录下查找。相关源码可到 https://github.com/gorden5566/jdk8u_hotspot.git 或 https://gitee.com/gorden5566/jdk8u_hotspot 下载。
到 hotspot 目录下搜索 JVM_InternString
1 | grep -nr "JVM_InternString" . |
找到如下信息:
1 | ./src/share/vm/prims/jvm.cpp:4060:JVM_ENTRY(jstring, JVM_InternString(JNIEnv *env, jstring str)) |
其实现如下:
1 | JVM_ENTRY(jstring, JVM_InternString(JNIEnv *env, jstring str)) |
源码跟踪
解析字符串
JNIHandles
是一个 c++ 类,它位于 hotspot 项目下,路径为 ./src/share/vm/runtime/jniHandles.cpp
,对应的头文件为 ./src/share/vm/runtime/jniHandles.cpp
JNIHandles::resolve_non_null
是一个内联方法,它用于把 jobject 类型的 handle 解析为 oop,并且保证返回结果不为空。代码如下:
1 | inline oop JNIHandles::resolve_non_null(jobject handle) { |
处理逻辑
StringTable
类也在 hotspot 项目下,路径为 ./src/share/vm/classfile/symbolTable.cpp
,intern 方法代码如下:
1 | oop StringTable::intern(oop string, TRAPS) |
生成局部引用
JNIHandles::make_local
相关代码
1 | jobject JNIHandles::make_local(JNIEnv* env, oop obj) { |
其他 native 实现
在 ./src/share/native
目录下还有其他的 native 实现
1 | ➜ native git:(master) ls |
执行 tree java
查看 java 目录下的文件,可以发现文件名与 java 中的类名一致。
1 | java |