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 { } |
- 执行
javac String.java -d .
生成 class 文件 - 执行
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 |
- 本文作者: gorden5566
- 本文链接: 如何查找 jdk 中的 native 实现 - gorden5566
- 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!