如何查找 jdk 中的 native 实现

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
2
3
4
5
6
7
8
9
package java.lang;

public final class String implements java.io.Serializable, Comparable<String>, CharSequence {

/**
* Returns a canonical representation for the string object.
*/
public native String intern();
}

注意,为了方便查看,这里删掉了不相关的代码。

按照生成规则生成本地方法名

根据 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
2
3
4
package java.lang;

public class String {
}
  1. 执行 javac String.java -d . 生成 class 文件
  2. 执行 javah java.lang.String 生成头文件

相关文件如下:

1
2
3
4
5
6
7
.
├── String.java
├── java
│   └── lang
│   └── String.class
├── java_lang_String.h
└── java_lang_String_CaseInsensitiveComparator.h

打开 java_lang_String.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class java_lang_String */

#ifndef _Included_java_lang_String
#define _Included_java_lang_String
#ifdef __cplusplus
extern "C" {
#endif
#undef java_lang_String_serialVersionUID
#define java_lang_String_serialVersionUID -6849794470754667710LL
/*
* Class: java_lang_String
* Method: intern
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_java_lang_String_intern
(JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

其中 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
2
3
4
5
6
7
8
#include "jvm.h"
#include "java_lang_String.h"

JNIEXPORT jobject JNICALL
Java_java_lang_String_intern(JNIEnv *env, jobject this)
{
return JVM_InternString(env, this);
}

这只是一个入口,它的实现与虚拟机相关,因此需要到 HotSpot 目录下查找。相关源码可到 https://github.com/gorden5566/jdk8u_hotspot.githttps://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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
JVM_ENTRY(jstring, JVM_InternString(JNIEnv *env, jstring str))
JVMWrapper("JVM_InternString");
JvmtiVMObjectAllocEventCollector oam;

// 入参不能为空
if (str == NULL) return NULL;

// 1. 解析字符串
oop string = JNIHandles::resolve_non_null(str);

// 2. 处理逻辑
oop result = StringTable::intern(string, CHECK_NULL);

// 3. 生成局部引用并返回
return (jstring) JNIHandles::make_local(env, result);
JVM_END

源码跟踪

解析字符串

JNIHandles 是一个 c++ 类,它位于 hotspot 项目下,路径为 ./src/share/vm/runtime/jniHandles.cpp ,对应的头文件为 ./src/share/vm/runtime/jniHandles.hpp

JNIHandles::resolve_non_null 是一个内联方法,它用于把 jobject 类型的 handle 解析为 oop,并且保证返回结果不为空。代码如下:

1
2
3
4
5
6
7
8
9
inline oop JNIHandles::resolve_non_null(jobject handle) {
assert(handle != NULL, "JNI handle should not be null");
oop result = *(oop*)handle;
assert(result != NULL, "Invalid value read from jni handle");
assert(result != badJNIHandle, "Pointing to zapped jni handle area");
// Don't let that private _deleted_handle object escape into the wild.
assert(result != deleted_handle(), "Used a deleted global handle.");
return result;
};

处理逻辑

StringTable 类也在 hotspot 项目下,路径为 ./src/share/vm/classfile/symbolTable.cpp,intern 方法代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
oop StringTable::intern(oop string, TRAPS)
{
if (string == NULL) return NULL;
ResourceMark rm(THREAD);
int length;
Handle h_string (THREAD, string);

// 转为unicode string
jchar* chars = java_lang_String::as_unicode_string(string, length, CHECK_NULL);

// 调用intern的重载方法
oop result = intern(h_string, chars, length, CHECK_NULL);

return result;
}

oop StringTable::intern(Handle string_or_null, jchar* name,
int len, TRAPS) {
// 计算hash值
unsigned int hashValue = hash_string(name, len);

// 将hash值转换为string table的index
int index = the_table()->hash_to_index(hashValue);

// 到bucket查找string
oop found_string = the_table()->lookup(index, name, len, hashValue);

// 找到了,直接返回
if (found_string != NULL) {
ensure_string_alive(found_string);
return found_string;
}

debug_only(StableMemoryChecker smc(name, len * sizeof(name[0])));
assert(!Universe::heap()->is_in_reserved(name),
"proposed name of symbol must be stable");

Handle string;
// 尝试尽可能重用string
if (!string_or_null.is_null()) {
string = string_or_null;
} else {
string = java_lang_String::create_from_unicode(name, len, CHECK_NULL);
}

#if INCLUDE_ALL_GCS
if (G1StringDedup::is_enabled()) {
// Deduplicate the string before it is interned. Note that we should never
// deduplicate a string after it has been interned. Doing so will counteract
// compiler optimizations done on e.g. interned string literals.
G1StringDedup::deduplicate(string());
}
#endif

// 因为the_table()可能在安全点发生变化,所以在获取它之前前,需要先获取StringTable_lock锁
oop added_or_found;
{
MutexLocker ml(StringTable_lock, THREAD);
// 将字符串添加到string table
added_or_found = the_table()->basic_add(index, string, name, len,
hashValue, CHECK_NULL);
}

ensure_string_alive(added_or_found);

return added_or_found;
}

生成局部引用

JNIHandles::make_local 相关代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
jobject JNIHandles::make_local(JNIEnv* env, oop obj) {
if (obj == NULL) {
return NULL; // ignore null handles
} else {
JavaThread* thread = JavaThread::thread_from_jni_environment(env);

// 确保obj指向的是堆上的保留区域(the reserved area of the heap)
assert(Universe::heap()->is_in_reserved(obj), "sanity check");
return thread->active_handles()->allocate_handle(obj);
}
}

jobject JNIHandleBlock::allocate_handle(oop obj) {
assert(Universe::heap()->is_in_reserved(obj), "sanity check");
if (_top == 0) {
// This is the first allocation or the initial block got zapped when
// entering a native function. If we have any following blocks they are
// not valid anymore.
for (JNIHandleBlock* current = _next; current != NULL;
current = current->_next) {
assert(current->_last == NULL, "only first block should have _last set");
assert(current->_free_list == NULL,
"only first block should have _free_list set");
current->_top = 0;
if (ZapJNIHandleArea) current->zap();
}
// Clear initial block
_free_list = NULL;
_allocate_before_rebuild = 0;
_last = this;
if (ZapJNIHandleArea) zap();
}

// Try last block
if (_last->_top < block_size_in_oops) {
oop* handle = &(_last->_handles)[_last->_top++];
*handle = obj;
return (jobject) handle;
}

// Try free list
if (_free_list != NULL) {
oop* handle = _free_list;
_free_list = (oop*) *_free_list;
*handle = obj;
return (jobject) handle;
}
// Check if unused block follow last
if (_last->_next != NULL) {
// update last and retry
_last = _last->_next;
return allocate_handle(obj);
}

// No space available, we have to rebuild free list or expand
if (_allocate_before_rebuild == 0) {
rebuild_free_list(); // updates _allocate_before_rebuild counter
} else {
// Append new block
Thread* thread = Thread::current();
Handle obj_handle(thread, obj);
// This can block, so we need to preserve obj accross call.
_last->_next = JNIHandleBlock::allocate_block(thread);
_last = _last->_next;
_allocate_before_rebuild--;
obj = obj_handle();
}
return allocate_handle(obj); // retry
}

其他 native 实现

./src/share/native 目录下还有其他的 native 实现

1
2
➜  native git:(master) ls
com common java sun

执行 tree java 查看 java 目录下的文件,可以发现文件名与 java 中的类名一致。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
java
├── io
│   ├── FileInputStream.c
│   ├── ObjectInputStream.c
│   ├── ObjectOutputStream.c
│   ├── ObjectStreamClass.c
│   ├── RandomAccessFile.c
│   ├── io_util.c
│   └── io_util.h
├── lang
│   ├── Class.c
│   ├── ClassLoader.c
│   ├── Compiler.c
│   ├── Double.c
│   ├── Float.c
│   ├── Object.c
│   ├── Package.c
│   ├── Runtime.c
│   ├── SecurityManager.c
│   ├── Shutdown.c
│   ├── StrictMath.c
│   ├── String.c
│   ├── System.c
│   ├── Thread.c
│   ├── Throwable.c
│   ├── fdlibm
│   │   ├── include
│   │   │   ├── fdlibm.h
│   │   │   └── jfdlibm.h
│   │   └── src
│   │   ├── e_acos.c
│   │   ├── e_asin.c
│   │   ├── e_atan2.c
│   │   ├── e_atanh.c
│   │   ├── e_cosh.c
│   │   ├── e_exp.c
│   │   ├── e_fmod.c
│   │   ├── e_hypot.c
│   │   ├── e_log.c
│   │   ├── e_log10.c
│   │   ├── e_pow.c
│   │   ├── e_rem_pio2.c
│   │   ├── e_remainder.c
│   │   ├── e_scalb.c
│   │   ├── e_sinh.c
│   │   ├── e_sqrt.c
│   │   ├── k_cos.c
│   │   ├── k_rem_pio2.c
│   │   ├── k_sin.c
│   │   ├── k_standard.c
│   │   ├── k_tan.c
│   │   ├── s_atan.c
│   │   ├── s_cbrt.c
│   │   ├── s_ceil.c
│   │   ├── s_copysign.c
│   │   ├── s_cos.c
│   │   ├── s_expm1.c
│   │   ├── s_fabs.c
│   │   ├── s_finite.c
│   │   ├── s_floor.c
│   │   ├── s_frexp.c
│   │   ├── s_ilogb.c
│   │   ├── s_isnan.c
│   │   ├── s_ldexp.c
│   │   ├── s_lib_version.c
│   │   ├── s_log1p.c
│   │   ├── s_logb.c
│   │   ├── s_matherr.c
│   │   ├── s_modf.c
│   │   ├── s_nextafter.c
│   │   ├── s_rint.c
│   │   ├── s_scalbn.c
│   │   ├── s_signgam.c
│   │   ├── s_significand.c
│   │   ├── s_sin.c
│   │   ├── s_tan.c
│   │   ├── s_tanh.c
│   │   ├── w_acos.c
│   │   ├── w_asin.c
│   │   ├── w_atan2.c
│   │   ├── w_atanh.c
│   │   ├── w_cosh.c
│   │   ├── w_exp.c
│   │   ├── w_fmod.c
│   │   ├── w_hypot.c
│   │   ├── w_log.c
│   │   ├── w_log10.c
│   │   ├── w_pow.c
│   │   ├── w_remainder.c
│   │   ├── w_scalb.c
│   │   ├── w_sinh.c
│   │   └── w_sqrt.c
│   ├── java_props.h
│   └── reflect
│   ├── Array.c
│   ├── Executable.c
│   ├── Field.c
│   └── Proxy.c
├── net
│   ├── DatagramPacket.c
│   ├── Inet4Address.c
│   ├── Inet6Address.c
│   ├── InetAddress.c
│   ├── net_util.c
│   └── net_util.h
├── nio
│   └── Bits.c
├── security
│   └── AccessController.c
└── util
├── TimeZone.c
├── concurrent
│   └── atomic
│   └── AtomicLong.c
└── zip
├── Adler32.c
├── CRC32.c
├── Deflater.c
├── Inflater.c
├── ZipFile.c
├── zip_util.c
├── zip_util.h
└── zlib
├── ChangeLog
├── README
├── compress.c
├── crc32.h
├── deflate.c
├── deflate.h
├── gzclose.c
├── gzguts.h
├── gzlib.c
├── gzread.c
├── gzwrite.c
├── infback.c
├── inffast.c
├── inffast.h
├── inffixed.h
├── inflate.c
├── inflate.h
├── inftrees.c
├── inftrees.h
├── patches
│   └── ChangeLog_java
├── trees.c
├── trees.h
├── uncompr.c
├── zadler32.c
├── zconf.h
├── zcrc32.c
├── zlib.h
├── zutil.c
└── zutil.h