java native方法深入理解

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


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提供接口动态调用流程如下:

  1. 准备好参数数据及其对应ffi_type数组、返回值内存指针、函数指针
  2. 创建与函数特征相匹配的函数原型:ffi_cif对象
  3. 使用“ffi_call”来完成函数调用

总结

最终native方法会根据规范在动态链接库.dll(win)或.so(linux)查找方法的地址 function ,然后使用ffi_call库将参数封装,调用动态库的函数的.以上是native调用的整个过程.

  • 14
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值