在学习JDK源码(concurrent并发包、Thread相关源码等)时,一层一层进入方法中,看到最底层通常都会看到一个native修饰的方法。

为什么到看JDK源码时,到native方法就没有了?native方法是干啥的?在哪里能看到native方法?java是如何调用native方法的?今天,就通过实际模拟,看看java是如何调用native方法的。

为了做这个测试,花了我两个晚上,遇到各种问题。为了解决这些问题,都不知道抽了多少根烟,掉了多少的头发。

上正文。

一、为什么会有native方法

java是偏上层的计算机语言,最终都需要在底层的操作系统上执行,而java是不能直接操作操作系统的。这就需要在java和操作系统之间,有一种类似语言转义的过程。

我们知道,C语言和C++语言可以和操作系统直接交互。JDK中native方法,可以将java操作指令转换成C和C++,从而实现和底层的操作系统交互。而将java操作转换成C和C++的过程就是JVM完成的,jvm(比如hotspot)的源码中有大量的C和C++的代码,这些代码就包含JDK中native方法的具体实现了。

这里想复习一下JDK、JRE、JVM之间的关系。JDK是Java开发工具包,是整个Java的核心,包括了Java运行环境JRE、Java工具和Java基础类库。JRE是JDK项目的一部分,是java的运行环境,包含JVM标准实现及Java核心类库。JVM是java虚拟机,是整个java实现跨平台的最核心的部分,能够运行以Java语言写作的软件程序。因此,JVM是连接java语言和操作系统的桥梁,java的”一次编译到处运行“,就是JVM屏蔽了不同操作系统的差异,因为在JVM模块中,同一个native方法会有不同的操作系统的实现,以满足不同操作系统的要求。因此,想了解native方法的具体实现,必须看JVM的代码。JVM的源码在哪里?当然在JDK的源码当中了。这里可以在查看不同版本的OpenJdk的代码,openJdk内部就有不同版本的hotspot的实现了。

今天的重点不是JDK的源码,这里就不细说了。

模拟Java调用c或c++写的native方法的技术叫做JNI(Java Native Interface)。JNI可以确保代码在不同的平台上方便的移植。

二、写一个简单的java对象

这里写一个简单的java类,使用javac编译、javap生产头文件、并使用java命令执行。

/*** Description: java调用C* java方法中有很多native方法,这些方法都是hotspot中用C或者C++实现的。* 下面模拟一个java调用C的过程* @author 诸葛小猿* @date 2020-11-11*/
public class JavaCallC {static {// 使用文件名加载自定义的C语言库System.load("/root/java-learn/libJavaCallC.so" );}public static void main(String[] args) {JavaCallC javaCallC =new JavaCallC();// 调用本地方法javaCallC.cMethod();}// 使用C语言实现本地方法private native void cMethod();
}

几个坑:

  • 为了后面不会出现各种幺蛾子,建议不要加包名。

  • 代码的第12行的库文件,后面会生成,注意文件的名字和路径。库文件也可以使用System.loadLibrary( "JavaCallC" )方式加载,这种方式加载要注意库的名字;

  • 代码的第24行,定义一个native方法。后面会使用c语言模拟实现。

三、获得JavaCallC.class文件

将上面的文件上传到Centos上,使用如下命令进行编译。

文件上传路径: /root/java-learn

在该路径下执行编译命令: java JavaCallC.java

该路径下会生成一个class文件:JavaCallC.class

四、获得JavaCallC.h文件

/root/java-learn路径下,使用javah命令生成头文件

在该路径下执行: javah JavaCallC。注意不要带后缀。

会在该路径下生成头文件:JavaCallC.h

上面的执行过程:

[root@iZuf61pdvb2o7cf4mu9ccyZ java-learn]#
[root@iZuf61pdvb2o7cf4mu9ccyZ java-learn]# pwd
/root/java-learn
[root@iZuf61pdvb2o7cf4mu9ccyZ java-learn]# ll
total 4
-rw-r--r-- 1 root root 635 Nov 12 23:45 JavaCallC.java
[root@iZuf61pdvb2o7cf4mu9ccyZ java-learn]# javac JavaCallC.java
[root@iZuf61pdvb2o7cf4mu9ccyZ java-learn]#
[root@iZuf61pdvb2o7cf4mu9ccyZ java-learn]# ll
total 8
-rw-r--r-- 1 root root 476 Nov 12 23:46 JavaCallC.class
-rw-r--r-- 1 root root 635 Nov 12 23:45 JavaCallC.java
[root@iZuf61pdvb2o7cf4mu9ccyZ java-learn]#
[root@iZuf61pdvb2o7cf4mu9ccyZ java-learn]# javah JavaCallC
[root@iZuf61pdvb2o7cf4mu9ccyZ java-learn]#
[root@iZuf61pdvb2o7cf4mu9ccyZ java-learn]# ll
total 12
-rw-r--r-- 1 root root 476 Nov 12 23:46 JavaCallC.class
-rw-r--r-- 1 root root 376 Nov 12 23:46 JavaCallC.h
-rw-r--r-- 1 root root 635 Nov 12 23:45 JavaCallC.java
[root@iZuf61pdvb2o7cf4mu9ccyZ java-learn]#

打开头文件,查看具体内容:

[root@iZuf61pdvb2o7cf4mu9ccyZ java-learn]# cat  JavaCallC.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class JavaCallC */#ifndef _Included_JavaCallC
#define _Included_JavaCallC
#ifdef __cplusplus
extern "C" {#endif
/** Class:     JavaCallC* Method:    cMethod* Signature: ()V*/
JNIEXPORT void JNICALL Java_JavaCallC_cMethod # 这里就是java文件中cMethod方法的签名。(JNIEnv *, jobject);#ifdef __cplusplus
}
#endif
#endif
[root@iZuf61pdvb2o7cf4mu9ccyZ java-learn]#

头文件的第16-17行很关键,他是上面java文件的cMethod方法的签名。在下面C语言实现这个方法时,方法的签名必须和这个方法一致

五、使用C语言模拟一个native方法

模拟一个c代码,文件名称Cclass.c

#include <stdio.h> //头文件
#include "JavaCallC.h" // java文件头,这里一定要加上上面java语言的头文件// 这就是上面头文件中的cMethod方法的具体实现,注意方法签名不能变,一定要和头文件一样。
JNIEXPORT void JNICALL Java_JavaCallC_cMethod(JNIEnv *env, jobject c1)
{// 如果java调用cMethod方法成功,则会打印这句话printf("Java_JavaCallC_cMethod call succ \n");
}// 以下所有的内容的内容是测试Cclass.c的语法的,可以省掉。
// 先声明 后调用
void test(){ printf("main C \n");}//main方法,程序入口,用于测试
int main(){ test();}

同样将Cclass.c上传到Centos上,文件上传路径: /root/java-learn

下面使用Cclass.c生成动态链接库文件:libJavaCallC.so

[root@iZuf61pdvb2o7cf4mu9ccyZ java-learn]# gcc  -fPIC -I /opt/jdk1.8.0_211/include  -I /opt/jdk1.8.0_211/include/linux   -shared -o libJavaCallC.so Cclass.c

很多坑:

  • 生成的库文件名字及路径一定要和上面java文件中加载的一致。其中-o libJavaCallC.so就是生成的库文件名字。如果使用使用的是System.loadLibrary()方式加载的库文件,则使用的库名称是: “JavaCallC”,而不是 "libJavaCallC"或 “libJavaCallC.so”。
  • JavaCallC.java文件中的native方法cMethod()在Cclass.c文件中的实现时,一定要和JavaCallC.h头文件中cMethed()的签名一致,一定要使用JNIEXPORT void JNICALL Java_JavaCallC_cMethod(JNIEnv *env, jobject c1)
  • Cclass.c中一定要在文件头中使用#include "JavaCallC.h"将头文件包含进来,不然编译和执行时找不到Java_JavaCallC_cMethod
  • 使用gcc编译时,因为Cclass.c中包含JavaCallC.h头文件,而JavaCallC.h头文件的第二行又包含#include <jni.h>头文件,而jni.h中又包含其他的头文件,gcc编译时,这些头文件的位置要指定。这些头文件都在jdk所在的目录中,这些目录的位置要使用参数-I进行指定。

运行后生成共享库(动态链接库)文件:libJavaCallC.so

编译完成后,共享库文件所在的目录加入到库文件的环境变量 LD_LIBRARY_PATH中。 LD_LIBRARY_PATH是Linux环境变量名,该环境变量主要用于指定查找共享库(动态链接库)时除了默认路径之外的其他路径。

[root@iZuf61pdvb2o7cf4mu9ccyZ java-learn]# export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/root/java-learn

六、执行java

通过上面的操作,在/root/java-learn目录下就会有如下的5个文件:

[root@iZuf61pdvb2o7cf4mu9ccyZ java-learn]# ll
total 24
-rw-r--r-- 1 root root  594 Nov 12 22:39 Cclass.c
-rw-r--r-- 1 root root  852 Nov 12 22:05 JavaCallC.class
-rw-r--r-- 1 root root  376 Nov 12 22:05 JavaCallC.h
-rw-r--r-- 1 root root 1108 Nov 12 22:04 JavaCallC.java
-rwxr-xr-x 1 root root 6179 Nov 12 22:39 libJavaCallC.so
[root@iZuf61pdvb2o7cf4mu9ccyZ java-learn]#

下面使用java JavaCallC命令在当前目录下执行我们的java程序:

[root@iZuf61pdvb2o7cf4mu9ccyZ java-learn]#
[root@iZuf61pdvb2o7cf4mu9ccyZ java-learn]# java JavaCallC
Java_JavaCallC_cMethod call succ
[root@iZuf61pdvb2o7cf4mu9ccyZ java-learn]#

通过执行打印的结果Java_JavaCallC_cMethod call succ可以看出,java调用到了native方法,并执行了C文件中的方法体,并打印出执行成功。

七、遇到的问题

在做这个测试时,遇到了各种问题。这里列出来:

[root@iZuf61pdvb2o7cf4mu9ccyZ java-learn]# java com.wuxiaolong.LB.Demo.Lesson1.JavaCallC
Error: Could not find or load main class com.wuxiaolong.LB.Demo.Lesson1.JavaCallC
[root@iZuf61pdvb2o7cf4mu9ccyZ java-learn]#
## 这个问题是因为最开始使用了包名,执行时报错,可以通过相关的配置解决,测试中我去掉了包名。[root@iZuf61pdvb2o7cf4mu9ccyZ java-learn]# java JavaCallC
Exception in thread "main" java.lang.UnsatisfiedLinkError: no JavaCallC in java.library.pathat java.lang.ClassLoader.loadLibrary(ClassLoader.java:1867)at java.lang.Runtime.loadLibrary0(Runtime.java:870)at java.lang.System.loadLibrary(System.java:1122)at JavaCallC.<clinit>(JavaCallC.java:16)
[root@iZuf61pdvb2o7cf4mu9ccyZ java-learn]#
## 这是因为加载时使用的时System.loadLibrary(),而库名写错了[root@iZuf61pdvb2o7cf4mu9ccyZ java-learn]# gcc  -fPIC -I /opt/jdk1.8.0_211/include -I /opt/jdk1.8.0_211/include/linux  -shared -o libJavaCallC.so Cclass.c
Cclass.c:2:53: error: Java_JavaCallC_cMethod.h: No such file or directory
[root@iZuf61pdvb2o7cf4mu9ccyZ java-learn]#
## 这好像是因为Cclass.c文件中没有使用: #include "JavaCallC.h"[root@iZuf61pdvb2o7cf4mu9ccyZ java-learn]# java JavaCallC
Exception in thread "main" java.lang.UnsatisfiedLinkError: JavaCallC.cMethod()Vat JavaCallC.cMethod(Native Method)at JavaCallC.main(JavaCallC.java:25)
[root@iZuf61pdvb2o7cf4mu9ccyZ java-learn]#
## 这是因为Cclass.c文件方法的签名和JavaCallC.h头文件中的不一致[root@iZuf61pdvb2o7cf4mu9ccyZ java-learn]# gcc  -fPIC -I /opt/jdk1.8.0_211/include  -shared -o libJavaCallC.so Cclass.c
In file included from JavaCallC.h:2,from Cclass.c:2:
/opt/jdk1.8.0_211/include/jni.h:45:20: error: jni_md.h: No such file or directory
In file included from JavaCallC.h:2,from Cclass.c:2:
/opt/jdk1.8.0_211/include/jni.h:63: error: expected ‘=’, ‘,’, ‘;’, ‘asm’ or ‘__attribute__’ before ‘jsize’
/opt/jdk1.8.0_211/include/jni.h:122: error: expected specifier-qualifier-list before ‘jbyte’
/opt/jdk1.8.0_211/include/jni.h:220: error: expected specifier-qualifier-list before ‘jint’
/opt/jdk1.8.0_211/include/jni.h:1869: error: expected specifier-qualifier-list before ‘jint’
/opt/jdk1.8.0_211/include/jni.h:1877: error: expected specifier-qualifier-list before ‘jint’
/opt/jdk1.8.0_211/include/jni.h:1895: error: expected specifier-qualifier-list before ‘jint’
/opt/jdk1.8.0_211/include/jni.h:1934: error: expected ‘=’, ‘,’, ‘;’, ‘asm’ or ‘__attribute__’ before ‘jint’
/opt/jdk1.8.0_211/include/jni.h:1937: error: expected ‘=’, ‘,’, ‘;’, ‘asm’ or ‘__attribute__’ before ‘jint’
/opt/jdk1.8.0_211/include/jni.h:1940: error: expected ‘=’, ‘,’, ‘;’, ‘asm’ or ‘__attribute__’ before ‘jint’
/opt/jdk1.8.0_211/include/jni.h:1944: error: expected ‘=’, ‘,’, ‘;’, ‘asm’ or ‘__attribute__’ before ‘jint’
/opt/jdk1.8.0_211/include/jni.h:1947: error: expected ‘=’, ‘,’, ‘;’, ‘asm’ or ‘__attribute__’ before ‘void’
In file included from Cclass.c:2:
JavaCallC.h:15: error: expected ‘=’, ‘,’, ‘;’, ‘asm’ or ‘__attribute__’ before ‘void’
Cclass.c:11: error: expected ‘=’, ‘,’, ‘;’, ‘asm’ or ‘__attribute__’ before ‘void’
[root@iZuf61pdvb2o7cf4mu9ccyZ java-learn]#
## 这是因为编译时少了参数 : -I /opt/jdk1.8.0_211/include/linux[root@iZuf61pdvb2o7cf4mu9ccyZ java-learn]# gcc  -fPIC -I /opt/jdk1.8.0_211/include -I /opt/jdk1.8.0_211/include/linux  -shared -o libJavaCallC.so Cclass.c
Cclass.c:19: error: expected ‘=’, ‘,’, ‘;’, ‘asm’ or ‘__attribute__’ before ‘void’
[root@iZuf61pdvb2o7cf4mu9ccyZ java-learn]#
## 这好像是因为Cclass.c文件方法的签名和JavaCallC.h头文件中的不一致[root@iZuf61pdvb2o7cf4mu9ccyZ java-learn]# gcc  -fPIC -I /opt/jdk1.8.0_211/include  -I /opt/jdk1.8.0_211/include/linux   -shared -o libJavaCallC.so Cclass.c
Cclass.c: In function ‘Java_JavaCallC_cMethod’:
Cclass.c:12: error: expected declaration specifiers before ‘printf’
Cclass.c:13: error: expected declaration specifiers before ‘}’ token
Cclass.c:13: error: expected ‘{’ at end of input
[root@iZuf61pdvb2o7cf4mu9ccyZ java-learn]#
## 这好像是因为Cclass.c文件方法的签名和JavaCallC.h头文件中的不一致

关注公众号,输入“java-summary”即可获得源码。

完成,收工!

传播知识,共享价值】,感谢小伙伴们的关注和支持,我是【诸葛小猿】,一个彷徨中奋斗的互联网民工。

java是如何调用native方法?hotspot源码分析必会技能相关推荐

  1. Java 8中Collectors.groupingBy方法空指针异常源码分析

    现在有这样的一个需求:老板让把所有的员工按年龄进行分组,然后统计各个年龄的人数. 这个需求,如果是在数据库中,可以直接使用一个 group by 语句进行统计即可,那么在 Java 中的话,可以借助于 ...

  2. react 调用组件方法_React源码分析1 — 组件和对象的创建(createClass,createElement)...

    1 组件的创建 学习了半年前端了,感觉前端的水确实也很深.做安卓的时候就对React-Native比较感兴趣,开发H5时也使用了一段时间的ReactJS.所以决定好好分析下它的源码.文章中有不对的地方 ...

  3. Java的三种代理模式完整源码分析

    Java的三种代理模式&完整源码分析 Java的三种代理模式&完整源码分析 参考资料: 博客园-Java的三种代理模式 简书-JDK动态代理-超详细源码分析 [博客园-WeakCach ...

  4. java中的==、equals()、hashCode()源码分析(转载)

    在java编程或者面试中经常会遇到 == .equals()的比较.自己看了看源码,结合实际的编程总结一下. 1. ==  java中的==是比较两个对象在JVM中的地址.比较好理解.看下面的代码: ...

  5. View的invalidate()方法的源码分析

    首先要明白invalidate()方法是做什么的? View#invalidate(): /*** Invalidate the whole view. If the view is visible, ...

  6. Java代码怎么取消订阅功能,RxJava2 中多种取消订阅 dispose 的方法梳理( 源码分析 )...

    Github 相关代码: Github地址 一直感觉 RxJava2 的取消订阅有点混乱, 这样也能取消, 那样也能取消, 没能系统起来的感觉就像掉进了盘丝洞, 迷乱... 下面说说这几种情况 几种取 ...

  7. java中的 dispose_RxJava2 中多种取消订阅 dispose 的方法梳理( 源码分析 )

    Github 相关代码: Github地址 一直感觉 RxJava2 的取消订阅有点混乱, 这样也能取消, 那样也能取消, 没能系统起来的感觉就像掉进了盘丝洞, 迷乱- 下面说说这几种情况 几种取消的 ...

  8. 这篇文章绝对让你深刻理解java类的加载以及ClassLoader源码分析

    前言 package com.jvm.classloader;class Father2{public static String strFather="HelloJVM_Father&qu ...

  9. java集合(6):TreeMap源码分析(jdk1.8)

    前言 TreeMap的基本概念: TreeMap集合是基于红黑树(Red-Black tree)的 NavigableMap实现.该集合最重要的特点就是可排序,该映射根据其键的自然顺序进行排序,或者根 ...

  10. Java并发编程笔记之Semaphore信号量源码分析

    JUC 中 Semaphore 的使用与原理分析,Semaphore 也是 Java 中的一个同步器,与 CountDownLatch 和 CycleBarrier 不同在于它内部的计数器是递增的,那 ...

最新文章

  1. selenium 状态码521_sqlmap对状态码404处理的bug
  2. 使用vue-router设置每个页面的title
  3. 大厂JVM GC面试题
  4. PHP个人博客网站设计 学生PHP个人博客网页源码 PHP MYSQL动态网站作品
  5. 进行Java Web项目开发需要掌握的技术
  6. 计算机图形学应用:java2d和3d_?硬核儿童节礼物:MIT学神、太极作者胡渊鸣送你一门计算机课程...
  7. Mysql远程连接报错2003 Cant connect toMySQL server on your address解决方案
  8. 在Finder中显示隐藏文件
  9. 微信红包架构设计,高并发系统应用实战
  10. 【数据库】E-R图相关知识、绘制方法及工具推荐
  11. python安装scipy库出错_解决scipy安装(pip install scipy)失败,以及其他问题
  12. 【日常踩坑】修复 chrome 打不开微信或者部分第三方应用内链接
  13. Shine——更简单的Android网络请求库封装
  14. 联发科mtk手机处理器怎么样_“传音”新款手机发布,设计大胆,搭载联发科G90T处理器...
  15. 阿里云企业版云服务器使用流程
  16. PS练习7——蒙版的使用
  17. 用了几年的iPhone 11竟然可以免费换电池
  18. 【渝粤题库】陕西师范大学165205 组织设计与人力资源规划 作业(专升本)
  19. JQuery判断字符串是否是数字
  20. What Is An NFT? Non-Fungible Tokens Explained

热门文章

  1. 《明朝那些事儿》读书笔记
  2. Linux停服务器命令,使用linux的shutdown命令关闭服务器
  3. 超级SIM卡 SEID号读取 手机NFC门禁刷卡模块方案
  4. GitHub上java的开源项目(java程序员必备)
  5. 如何导出久其报表所有数据_久其报表制作与分发统一服务平台解决方案
  6. 爬取超星考试题目_2020超星测试题库导入网课答案
  7. 2022显卡、CPU天梯图
  8. 解决删除文件时出现“无法读取源文件或磁盘”的办法
  9. xshell安卓版下载_xshell5手机安卓版下载|Xshell5最新版下载_v5.0.1199_9ht安卓下载
  10. c语言标准库详解(五):stdio.h之直接IO/文件定位/错误处理