提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
java native方法深入理解
前言
提示:这里可以添加本文要记录的大概内容:
例如:相信很多java程序员对native方法和普通方法是如何运行以及底层原理很好奇.这篇文章是对此机制做一个技术分享。
提示:以下是本篇文章正文内容,下面案例可供参考
一、java字节码层面分析
示例:查看java编译以后的方法层面的字节码,如下是普通方法的界面查找实例。
1.普通方法demo
代码如下(示例):
public class Demo{
public static void main(String[] args) {
int sum = sum(1,2);
System.out.println(sum);
}
public static int sum(int a,int b){
return a+b;
}
}
2.普通方法字节码
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=1
0: iconst_1
1: iconst_2
2: invokestatic #2 // Method sum:(II)I
5: istore_1
6: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
9: iload_1
10: invokevirtual #4 // Method java/io/PrintStream.println:(I)V
13: return
3.native方法demo
public class Demo{
static{
System.loadLibrary("CH347DLLA64.dll");
}
public static void main(String[] args) {
Demo demo = new Demo();
int sum = demo.sum(1,2);
System.out.println(sum);
}
public native int sum(int a,int b);
}
4.native方法字节码
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=3, args_size=1
0: new #2 // class Demo
3: dup
4: invokespecial #3 // Method "<init>":()V
7: astore_1
8: aload_1
9: iconst_1
10: iconst_2
11: invokevirtual #4 // Method sum:(II)I
14: istore_2
15: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
18: iload_2
19: invokevirtual #6 // Method java/io/PrintStream.println:(I)V
22: return
public native int sum(int, int);
descriptor: (II)I
flags: ACC_PUBLIC, ACC_NATIVE
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: ldc #7 // String CH347DLLA64.dll
2: invokestatic #8 // Method java/lang/System.loadLibrary:(Ljava/lang/String;)V
5: return
LineNumberTable:
line 3: 0
line 4: 5
}
字节码层面总结
1. 调用static方法字节码: invokestatic
2. 调用实例方法字节码: invokevirtual
3. 调用类的构造方法: invokespecial
4. 调用类的接口方法: invokeinterface
5. 调用类的动态方法(下一篇解析):invokedynamic
6. 调用native方法: invokevirtual, 以上可以推出native方法的调用和普通方法 字节码一致。差异在方法的修饰符 flags: ACC_PUBLIC, ACC_NATIVE.
二、jvm层面如何执行native方法
1.jvm字节码解释器bytecodeInterpreter.cpp
代码如下(示例):
CASE(_invokevirtual):
CASE(_invokespecial):
CASE(_invokestatic): {
u2 index = Bytes::get_native_u2(pc+1);
ConstantPoolCacheEntry* cache = cp->entry_at(index);
...
istate->set_callee(callee);
//方法中调用设置方法的入口点
istate->set_callee_entry_point(callee->from_interpreted_entry());
#ifdef VM_JVMTI
if (JvmtiExport::can_post_interpreter_events() && THREAD->is_interp_only_mode()) {
istate->set_callee_entry_point(callee->interpreter_entry());
}
#endif /* VM_JVMTI */
istate->set_bcp_advance(3);
UPDATE_PC_AND_RETURN(0); // I'll be back...
}
}
2.方法的入口点设置
a. 设置入口点的地方,反向查找method.hpp
void set_interpreter_entry(address entry) { _i2i_entry = entry; _from_interpreted_entry = entry; }
b. 方法链接时设置,继续查找method.hpp
c. 查看链接过程method.cpp
void Method::link_method(methodHandle h_method, TRAPS) {
// _i2i_entry 先不关注,这个解析器和即时编译转换的入口点
if (_i2i_entry != NULL) return;
assert(_adapter == NULL, "init'd to NULL" );
assert( _code == NULL, "nothing compiled yet" );
// Setup interpreter entrypoint
assert(this == h_method(), "wrong h_method()" );
//重点就在这里,这次设置方法入口点
address entry = Interpreter::entry_for_method(h_method);
assert(entry != NULL, "interpreter entry must be non-null");
set_interpreter_entry(entry);
}
}
d. 设置方法入口的例程, 以c++解释器为例子(都是c代码便于理解)
enum MethodKind {
... //例程类型
native, //native method
native_synchronized, //
...
};
e. 安装例程,在jvm初始化调用InterpreterGenerator(不同解释器有不同的例程生成)
address AbstractInterpreterGenerator::generate_method_entry(
AbstractInterpreter::MethodKind kind) {
...
case Interpreter::native:
entry_point = ((InterpreterGenerator*) this)->generate_native_entry(false); //native方法入例程
break;
if (entry_point == NULL)
entry_point = ((InterpreterGenerator*) this)->generate_normal_entry(false);//普通方法入例程
return entry_point;
}
f. 执行nativa方法
int CppInterpreter::native_entry(Method* method, intptr_t UNUSED, TRAPS) {
**// 获取native方法对应的c的函数地址**
address function= method->native_function();
//此处是修改当前当前的执行状态
ThreadStateTransition::transition_from_java(thread, _thread_in_native);
//实际是通过ffi_call(Foreign Function Interface)库函数调用实际的c方法,按照如参数顺序和参数类型封装并调用将返回值进行封装.
intptr_t result[4 - LogBytesPerWord];
ffi_call(handler->cif(), (void (*)()) function, result, arguments);
...
// 其中涉及安全域,切换线程状态会做对应的安全性检查.
if (SafepointSynchronize::do_call_back() ||
thread->has_special_condition_for_native_trans()) {
JavaThread::check_special_condition_for_native_trans(thread);
CHECK_UNHANDLED_OOPS_ONLY(thread->clear_unhandled_oops());
}
使用libffi提供接口动态调用流程如下:
- 准备好参数数据及其对应ffi_type数组、返回值内存指针、函数指针
- 创建与函数特征相匹配的函数原型:ffi_cif对象
- 使用“ffi_call”来完成函数调用
总结
最终native方法会根据规范在动态链接库.dll(win)或.so(linux)查找方法的地址 function ,然后使用ffi_call库将参数封装,调用动态库的函数的.以上是native调用的整个过程.