JNIEnv

1
2
3
4
5
6
public class Hello{
private static native void say();
public static void main(String[] args){
say();
}
}
1
2
3
4
// 编译 Hello.java 生成 Hello.class
javac Hello.java
// 生成 Hello.h
javah Hello
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class Hello */

#ifndef _Included_Hello
#define _Included_Hello
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: Hello
* Method: say
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_Hello_say
(JNIEnv *, jclass);

#ifdef __cplusplus
}
#endif
#endif

JNIEnv 是 JNI 接口。可以认为 JNI 是 JVM 提供的一个和 Native 方法(使用平台相关的代码实现的方法)交流(调用 JVM 功能)的接口。

JNIEnv 的结构

定义在 jni.h 中

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
struct JNIEnv_;

typedef JNIEnv_ JNIEnv;

struct JNIEnv_ {
const struct JNINativeInterface_ *functions;
#ifdef __cplusplus

// JNI 向外部提供的接口
jint GetVersion() {
return functions->GetVersion(this);
}
jclass DefineClass(const char *name, jobject loader, const jbyte *buf,
jsize len) {
return functions->DefineClass(this, name, loader, buf, len);
}
jclass FindClass(const char *name) {
return functions->FindClass(this, name);
}
jmethodID FromReflectedMethod(jobject method) {
return functions->FromReflectedMethod(this,method);
}
jfieldID FromReflectedField(jobject field) {
return functions->FromReflectedField(this,field);
}

// ...

jobject NewDirectByteBuffer(void* address, jlong capacity) {
return functions->NewDirectByteBuffer(this, address, capacity);
}
void* GetDirectBufferAddress(jobject buf) {
return functions->GetDirectBufferAddress(this, buf);
}
jlong GetDirectBufferCapacity(jobject buf) {
return functions->GetDirectBufferCapacity(this, buf);
}
jobjectRefType GetObjectRefType(jobject obj) {
return functions->GetObjectRefType(this, obj);
}
}


// 函数指针表
// 对于不同的 JVM 实现JNI就是实现 JNI 这个结构体就是
struct JNINativeInterface_ {
// 前四个预留指针
void *reserved0;
void *reserved1;
void *reserved2;

void *reserved3;
jint (JNICALL *GetVersion)(JNIEnv *env);

jclass (JNICALL *DefineClass)
(JNIEnv *env, const char *name, jobject loader, const jbyte *buf,
jsize len);
jclass (JNICALL *FindClass)
(JNIEnv *env, const char *name);

jmethodID (JNICALL *FromReflectedMethod)
(JNIEnv *env, jobject method);
jfieldID (JNICALL *FromReflectedField)
(JNIEnv *env, jobject field);

// ...

jobject (JNICALL *NewDirectByteBuffer)
(JNIEnv* env, void* address, jlong capacity);
void* (JNICALL *GetDirectBufferAddress)
(JNIEnv* env, jobject buf);
jlong (JNICALL *GetDirectBufferCapacity)
(JNIEnv* env, jobject buf);

/* New JNI 1.6 Features */

jobjectRefType (JNICALL *GetObjectRefType)
(JNIEnv* env, jobject obj);
}

结构图:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
				 +-----------+			 +------------------+ <------}
JNIEnv *env ---> + functions + --------> + NULL + |
+-----------+ +------------------+ |
+ NULL + |
+------------------+ | <--- 预留指针
+ NULL + |
+------------------+ |
+ NULL + |
+------------------+ <------}
+ GetVersion + |
+------------------+ |
+ DefineClass + |
+------------------+ |
+ FindClass + |
+------------------+ |
+ ...... + | <--- 函数指针表
+------------------+ |
+ ...... + |
+------------------+ |
+ ...... + |
+------------------+ |
+ GetObjectRefType + |
+------------------+ <------}

这种结构的接口的好处是,各个函数全部以函数指针的形式提供给 native 方法。这样 native 方法。
native 方法只使用函数指针。而不会引用入 GetVersion 等等这样的名字。同时只要同一平台上的JVM
遵守上面的 函数指针表 结构,那么使用到 JNIEnv 指针的 native 代码是不需要重新编译的。这也是 JNI 设计的初衷。这样同平台的 native 代码就可以跨 该平台上 所有实现 JNI 的 JVM,并且不需要重新编译。

hotspot 实现

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
// hotspot\src\share\vm\prims\jni.cpp中定义了
// Structure containing all jni functions
struct JNINativeInterface_ jni_NativeInterface = {
NULL,
NULL,
NULL,

NULL,

jni_GetVersion,

jni_DefineClass,
jni_FindClass,

jni_FromReflectedMethod,
jni_FromReflectedField,

// ...

jni_NewDirectByteBuffer,
jni_GetDirectBufferAddress,
jni_GetDirectBufferCapacity,

// New 1_6 features

jni_GetObjectRefType
};

// Returns the function structure
struct JNINativeInterface_* jni_functions() {
#ifndef JNICHECK_KERNEL
if (CheckJNICalls) return jni_functions_check();
#else // JNICHECK_KERNEL
if (CheckJNICalls) warning("-Xcheck:jni is not supported in kernel vm.");
#endif // JNICHECK_KERNEL
return &jni_NativeInterface;
}

每一个线程中都有一个 JNIEnv 变量

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
class JavaThread: public Thread {

// 当这个线程调用一个 native 方法的时候
// 就会将这个字段作为第一个参数 JNIEnv *
JNIEnv _jni_environment;

//JNI functiontable getter/setter for JVMTI jni function table interception API.
void set_jni_functions(struct JNINativeInterface_* functionTable) {
_jni_environment.functions = functionTable;
}
struct JNINativeInterface_* get_jni_functions() {
return (struct JNINativeInterface_ *)_jni_environment.functions;
}

// Returns the jni environment for this thread
JNIEnv* jni_environment() { return &_jni_environment; }
}


void JavaThread::initialize() {

// ...

// jni_functions() 定义在 jni.cpp 中
set_jni_functions(jni_functions());

// ...
}

java语言

1
java Hello

当执行一个 Hello 类的时候,发生了什么:

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
				 native-thread
java Hello ---> |
|
LoadJavaVM ---> |
|
_beginthreadex ---> |
|\
| \
| \
| \ main-thread
| \
| \
| |
| |<--- InitializeJVM
| |
| |<--- (*env)->GetStaticMethodID
| |
| |<--- (*env)->CallStaticVoidMethod
| |
| |
| o
| Wait for all non-daemon threads to end
|
o
wait on main-thread until main-thread to end.


main-thread(c++代码)
|
|<--- InitializeJVM
|
|<--- (*env)->GetStaticMethodID
|
|<--- (*env)->CallStaticVoidMethod
|
|<--- JavaCalls::call
|
|<--- 加载主类 Hello.class 文件
|
|<--- 生成 Hello.class 相关的内存数据,例如方法区,
|
|<--- 解析 Hello.class 中使用到其它类,并将其加载
|
|<--- 由于 Hello 类中有 native 方法,所以要使用 System.loadLibrary 将 Hello.dll 载入
|
|<--- 准备好执行环境,向栈中添加新的方法调用 stack frame.
|
|<--- 使用 java 代码解释引擎,执行加载在方法区中 Hello.main 方法
|
|<--- 执行 main
|
|<--- 直到遇到 native say 方法。
|
|<--- 找到这个 native 方法的地址(类加载的已经将 Hello.dll 载入,并且注册到 JVM)
|
|<--- 直接调用这个 native 方法: say(env, jclass). 之所以可以直接调用就是
| 因为,native 方法的实现和此时的 JVM 是同一个平台的。所以可以直接调用。
|
|<--- 执行 native.
|
|<--- 由于是 native 代码,所以此时 java 代码解释引擎 就不需要使用了
|
|<--- native 方法执行完毕,返回
|
|<--- java 代码解释引擎,继续执行 java 代码
|
|<--- main 方法结束
|
|<--- 返回到 JVM 环境
|
o
Wait for all non-daemon threads to end

可以看到,在 win32 平台上执行 Hello 类,其实就是 java.exe 和 jvm.dll 中提供的 c++
编写的功能,例如解析 class 文件,使用 解析器 来实现 class 文件中代码的执行等等。所以在
java Hello 运行的时候,就已经不存在所谓 java 类了,所以执行过程全部是由 c/c++ 编写
的 jvm.dll, Hello.dll 等等这些来解释执行 java 代码。自然地,所谓 new Hello() 所对应的
应该就是 java 代码解析器 会在堆内存中分配内存欧博,创建一个可以存储在 Hello 类所声明的字段
的数据结构。返回内存地址,这个内存地址就是新创建的 Java 对象。其中包含着 Hello 类的各种
字段。所有针对 这个 Java 对象的操作,最终还是由 c/c++ 代码来操作 内存中的这个 c/c++ 数据
结构(可以代表 Hello 类)来实现的。

JavaCalls::call

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
// hotspot\src\share\vm\runtime\javaCalls.cpp 
void JavaCalls::call(JavaValue* result, methodHandle method, JavaCallArguments* args, TRAPS) {
// Check if we need to wrap a potential OS exception handler around thread
// This is used for e.g. Win32 structured exception handlers
assert(THREAD->is_Java_thread(), "only JavaThreads can make JavaCalls");
// Need to wrap each and everytime, since there might be native code down the
// stack that has installed its own exception handlers
os::os_exception_wrapper(call_helper, result, &method, args, THREAD);
}

// JavaCalls::call 调用 call_helper
void JavaCalls::call_helper(JavaValue* result, methodHandle* m, JavaCallArguments* args, TRAPS) {

// ...

// do call
{ JavaCallWrapper link(method, receiver, result, CHECK);
{ HandleMark hm(thread); // HandleMark used by HandleMarkCleaner

// StubRoutines::call_stub() 返回一个函数指针 CallStub
// 然后调用该函数
StubRoutines::call_stub()(
(address)&link,
// (intptr_t*)&(result->_value), // see NOTE above (compiler problem)
result_val_address, // see NOTE above (compiler problem)
result_type,
method(),
entry_point,
args->parameters(),
args->size_of_parameters(),
CHECK
);

result = link.result(); // circumvent MS C++ 5.0 compiler bug (result is clobbered across call)
// Preserve oop return value across possible gc points
if (oop_result_flag) {
thread->set_vm_result((oop) result->get_jobject());
}
}
} // Exit JavaCallWrapper (can block - potential return oop must be preserved)

// ...
}
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
// hotspot\src\share\vm\runtime\stubRoutines.hpp
// Calls to Java
typedef void (*CallStub)(
address link,
intptr_t* result,
BasicType result_type,
methodOopDesc* method,
address entry_point,
intptr_t* parameters,
int size_of_parameters,
TRAPS
);

static CallStub call_stub() { return CAST_TO_FN_PTR(CallStub, _call_stub_entry); }

// hotspot\src\cpu\x86\vm\stubGenerator_x86_32.cpp
StubRoutines::_call_stub_entry = generate_call_stub(StubRoutines::_call_stub_return_address);

address generate_call_stub(address& return_address) {
StubCodeMark mark(this, "StubRoutines", "call_stub");
address start = __ pc();

// ...
// stub code
__ enter();
__ movptr(rcx, parameter_size); // parameter counter
__ shlptr(rcx, Interpreter::logStackElementSize); // convert parameter count to bytes
__ addptr(rcx, locals_count_in_bytes); // reserve space for register saves
__ subptr(rsp, rcx);
__ andptr(rsp, -(StackAlignmentInBytes)); // Align stack

// ...

// save rdi, rsi, & rbx, according to C calling conventions
__ movptr(saved_rdi, rdi);
__ movptr(saved_rsi, rsi);
__ movptr(saved_rbx, rbx);

// ...

// call Java function
__ BIND(parameters_done);
__ movptr(rbx, method); // get methodOop
__ movptr(rax, entry_point); // get entry_point
__ mov(rsi, rsp); // set sender sp
BLOCK_COMMENT("call Java function");
__ call(rax);

// ...
__ BIND(is_double);
// interpreter uses xmm0 for return values
if (UseSSE >= 2) {
__ movdbl(Address(rdi, 0), xmm0);
} else {
__ fstp_d(Address(rdi, 0));
}
__ jmp(exit);

// ...
return start;

generate_call_stub 代码看起来像是 汇编 ,其中

1
2
3
4
5
6
7
8
#define __ _masm->

// 这个 masm 是 ,应该就和 class 文件中的 字节码 执行有关
_masm = new MacroAssembler(code);
// MacroAssembler 定义在
// hotspot\src\share\vm\c1\c1_MacroAssembler.hpp

// hotspot\src\share\vm\interpreter 和字节码解释器相关的源码