如何查找 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 的源码,可以从 GitHub - gorden5566/jdk8u_jdk 下载,如果速度慢也可以从 jdk8u_jdk: openjdk8,解决了官方源码编译时大部分可能会遇到的问题 下载。

有了源码后,下一个问题就是如何找到对应的实现。因为 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 目录下查找。相关源码可到 GitHub - gorden5566/jdk8u_hotspot 或 jdk8u_hotspot: openjdk8,解决了官方源码编译时大部分可能会遇到的问题 下载。

到 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.cpp

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
  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值