JNI
2024-12-01 08:57:40 0 举报
AI智能生成
JNI (Java Native Interface) 是一种允许Java代码和其他编程语言(尤其是C和C++)进行交互的机制。它允许Java程序员在Java代码中调用本地方法,这些方法通常是用C或C++编写的。这种机制使得Java程序能够访问操作系统的底层功能,例如文件I/O、图形处理和网络通信。JNI是Java平台不可或缺的一部分,因为它提供了与各种底层系统和硬件进行交互的能力。
作者其他创作
大纲/内容
JNI如何实现Java与C++互通
JNI是Java Native Interface的缩写<br>Java可以通过JNI作为桥梁实现Java程序与C语言、C++编写的程序互调
什么场景下会用到JNI<br>1.用Java实现不了或者实现起来比较复杂。比如人脸识别、图像识别<br>2.驱动硬件
什么是动态链接库?<br>动态链接库:Linux平台下以【.so】结尾、Windows平台以下【.dll】结尾。程序编译时不会打包进可执行文件。<br>静态链接库:程序编译时会打包进可执行文件.相比动态链接库,生成的可执行文件更大。但是可独立运行
如何生成?<br>C语言用gcc编译,C++用g++编译<br>编译的时候,不需要放.h文件
C语言<br>/usr/bin/gcc -dynamiclib -l /Library/JavaVirtualMachines/openjdk-12.-.2.jdk/Contents/Home/include source/com_ziya_jni_TestPrint.cpp -o /lib/libjni.so
C++<br>/usr/bin/g++ -dynamiclib -l /Library/JavaVirtualMachines/openjdk-12.-.2.jdk/Contents/Home/include source/com_ziya_jni_TestPrint.cpp -o /lib/libjni.so
生成的动态链接库放哪儿Java程序能找到<br>System.out.println(System.getProperty("java.library.path"));
如何使用<br>文件名格式:lib+文件名+".so",真正在用的时候写文件名就行了。比如libhello.so<br>System.loadLibraray("hello");
JNI头文件
如何生成<br>javac [-encoding utf8] -h targetDir sourceFile<br><br>eg:javac -h /home/ziya/IdeaProjects/java-research/jni /home/ziya/IdeaProjects/java-research/src/main/java/com/luban/jni/MyJniTest.java
IDE配置external tool生成<br>$FileDirRelativeToProjectRoot$/$FileName$<br>-h<br>./jni<br>
注意<br>1.只要Java文件中有native方法才会生成JNI头文件<br>2.如果Java程序中引用了其他的类,比如extends,需要关联引用的文件才能生成JNI头文件
函数原型解释<br>如果是静态方法,第二个参数是jclass类型。如果是非静态方法,第二个参数是jobject类型,即this.
handle模型<br>在oop模型、klass模型外面又包装了一层。<br>JNI_ENTRY(jobject, jni_NewGlobalRef(JNIEnv *env, jobject ref))<br> JNIWrapper("NewGlobalRef");<br><br> Handle ref_handle(thread, JNIHandles::resolve(ref));<br> jobject ret = JNIHandles::make_global(ref_handle);<br><br> return ret;<br>JNI_END<br>
开发JNI程序<br>
需要配置头文件所在路径<br><br>include_directories("/home/ziya/Documents/openjdk/build/linux-x86_64-normal-server-slowdebug/jdk/include/")<br>include_directories("/home/ziya/Documents/openjdk/build/linux-x86_64-normal-server-slowdebug/jdk/include/linux")<br>
JNI相关API所在位置(jni.h)<br>可以以C语言的方式调用,也可以以C++的方式调用,略有不同<br>
C语言端<br>生成.so文件<br>/usr/bin/gcc -dynamiclib -shared -fpic -I/home/ziya/Documents/openjdk/build/linux-x86_64-normal-server-slowdebug/jdk/include/ -I/home/ziya/Documents/openjdk/build/linux-x86_64-normal-server-slowdebug/jdk/include/linux com_luban_jni_MyJniTest.c -o /lib/libjni_my.so<br><br>如果遇到jni_md.h文件找不到,需要将编译好的openjdk目录中的jni_md.h拷贝到它的上一层<br>jni_md.h文件找不到,需要将/home/ziya/Documents/openjdk/build/linux-x86_64-normal-server-slowdebug/jdk/include/linux下的jni_md.h<br>进入到上面的目录中执行cp jni_md.h ../即可<br><br>CLion的external tool配置<br>Program: /usr/bin/gcc<br>Arguments:<br>-shared<br>-fpic<br>-I/home/ziya/Documents/openjdk/build/linux-x86_64-normal-server-slowdebug/jdk/include/<br>com_qimingnan_jni_JniTest.c<br>-o<br>/lib/libjni.so<br><br>Working Directory: $ProjectFileDir$
java端<br>生成.h头文件<br>javac -h /home/ziya/IdeaProjects/java-research/jni /home/ziya/IdeaProjects/java-research/src/main/java/com/luban/jni/MyJniTest.java<br>
使用JNI将内存池接入
一般来说,大家接触到的GC收集器的所有名词都是一个独立的实体:内存管理器、垃圾分配策略、<br>屏障、信息收集器、记忆集、卡表......我们这里只聚焦于内存管理器、垃圾分配策略。
1.PS<br>内存管理器:ParalelScavengeHeap<br>垃圾回收策略:PSParallelCompact、PSMarkSweep(ParallelScavengeHeap::do_full_collection)<br>自调节控制器:PSAdaptiveSizePolicy<br><br>大对象判断如图
2.Serial(我们的JVM参考这个去实现)<br>内存管理器:GenCollectedHeap<br>垃圾回收策略:MarkSweepPolicy(Serial)、ASConcurrentMarkSweepPolicy与ConcurrentMarakSweepPolicy(CMS)
3.G1<br>内存管理器:G1CollectedHeap<br>垃圾回收策略:G1CollectorPolicyExt
JVM的堆区是何时如何创建的<br>注意理解一句话:不是由GC算法决定内存模型,而是希望支持什么样的GC算法,需要设计什么样的内存模型
根据不同的垃圾回收策略去创建不同的内存模型
当确定好Universe::_collectedHeap(垃圾回收策略之后),接着会调用initialize()
以G1收集器为例,接着就是要向操作系统申请内存。具体实现在reserve_heap中
reserve_heap方法,最终一定会调用到os目录中的代码
接入思路<br>以永久代的方式存储klass模型<br>1.模拟启动JVM初始化堆<br>2.重写类解析器,将klass模型写入永久代<br>3.模拟创建对象,将oop模型写入新生代
Unsafe.allocateMemory()<br>C++中new一个对象其实是在一个进程的堆中创建的,HotSpot要想new在自己的堆中,肯定要重写new,重写了new,对应的delete方法也要重写
自实现OOP机制
OOP作为Java语言的基石,所有的特性都需要此基石支撑。<br>JVM的三种模型<br>1.oop模型: InstanceOop<br>2.Klass模型 InstanceKlass InstanceMirrorKlass<br>3.handle模型 jint(int类型做了一层包装)
JVM实现思路<br>1.用C++从0开始,自己实现所有<br>2.用C++从0开始,jdk部分用openjdk的,比如System类<br>3.用JNI作为桥梁,一点一点的接入。比如内存池。openJDK有的通过JNI调用,比如System
OOP实现思路<br>1.传统方式(先以这种方式实现)<br>2.内存编织方式(进阶实现)
klass状态<br>enum Classstate {<br>allocated, // allocated (but not yet linked)<br>loaded, // loaded and inserted in class hierarchy (but not linked yet)<br>linked, // successfully linked/verfied (but not initialized yet)<br>being_initialized, // currently running class initializer<br>fully_initialized, // initialied (successfull final state)<br>initialization_error, // error happened during initialization<br>}<br><br>allocated状态表示已经分配内存,在InstanceKlass的构造函数中通常会将_init_state初始化为这个状态,<br>loaded状态表示类已经装载并且已经插入到继承体系中,在SystemDictionary:add_to_hierarchy()方法中会更新<br>linked状态标识已经成功连接/校验,只在InstanceKlass::link_class_impl()方法中更新为这个状态<br><br>另外3个状态是在类的初始化方法InstanceKlass::initialize_impl()中使用<br>初始化之前,会执行<clinit>静态代码块,初始化之后会上锁,保证线程安全<br><br>1.klass-oop-handle体系,还要实现mirrorKlass<br>2.用C++重写类解析器,生成Java类对应的Klass对象、mirrorKlass对象<br>3.实现方法初始化<br>4.实现对象创建(无继承无实现)<br>5.实现属性读写(静态、非静态)<br>6.实现方法调用(重复调用、嵌套调用)<br>7.实现继承(无属性无方法)<br>8.实现属性继承、属性读写(非多态访问、多态访问)<br>9.属性方法继承、方法访问(非多态访问、多态访问)<br>10.接口实现<br>11.多态访问
封装<br>继承<br>多态<br><br>一个类有属性和方法,可以继承和实现其他类。<br>属性分为静态和非静态<br><br>new指令会触发父类的加载<br>new一个对象对应的四条字节码指令<br>new 创建一个空对象,内存地址入栈<br>dup 复制栈顶元素,接下来要调用init非静态方法,但this指针还是空的,所以需要把栈顶元素弹出去,给this指针赋值,然后再把元素压入栈<br>invokespecial <init> 调用init方法<br>astore_1 pop出元素,赋值给index=1的局部变量表<br><br>加载阶段<br>SytemDictionary::resolve_or_null -> parseClassFile<br><br>link阶段的触发是在合适的阶段被触发的,例如new一个对象时 link_class.<br>HotSpot源码中是有验证阶段和解析阶段的,但是没有准备阶段(为类变量分配内存,设置默认值。但是在到达初始化。之前,类变量都没有初始化为真正的初始值)<br><br>多态<br>invokevirtual 类继承相关<br>invokeinterface 接口实现相关<br>vtable dispatch 虚表分发<br>
类的初始化initialized_impl<br>parseClassFile()->parseFields()->调整变量内存布局
layout_fields(class_loader, &fac, &parsed_annotations, &info, CHECK_NULL);
三种内存编织规则
解析属性,将会创建short[length][6]<br>接着将它解析之后放入FieldInfo Map<String, Integer>map,字段会有一个签名
JVM是如何调用Java方法的
多态的体现及相关指令<br>1.类的继承 invokevirtual vtable<br>2.接口实现 invoke interface itable<br><br>Jvm中调用方法<br>invokestatic 调用静态方法<br>invokespecial private、构造方法<br>invokevirtual 多态方法,虚表分发 (vtable dispatch)<br>invokeinterface 调用接口实现<br>invokedynamic lambda表达式
如何实现<br>通过父类引用访问子类方法,底层是借助虚表实现的。如图,其底层就是用数组来存储额。越处于继承链的顶端,其中的方法越靠前存储。<br>在C++中,这张表叫虚表。在JVM中,这张表叫做vtable,位置在klass对象的后面。<br>只要是通过invokevirtual指令调用的方法都需要写入vtable.哪些方法通过invokevirtual指令调用呢?被protected、public修饰,且不被final、static修饰的方法。满足这些条件的方法又称为虚方法。那native方法要插入vtable吗? 要<br>写程序实现时,要先处理parent.parent的虚方法,再处理parent的虚方法,再处理自己的虚方法。具体细节:<br>1.因为Object类,自实现的JVM是不解析的,那顶层的父类的super klass是NULL,所以程序需要处理 <br>a.super klass为空<br>b.super klass不为空<br>2.因为不知道继承链有多长,虚表递归处理
何时写入vtable? 可以有两个选择<br>1.解析字节码文件的时候<br>2.需要用到虚表的时候,即调用的时候。JVM是选择这个时机触发插入的。具体是link阶段<br><br><br>JVM在执行这些字节码指令之前进行link:anewarray、checkcast、getfield、getstatic、instanceof、invokedynamic、invokeinterface、invokespecial、invokestatic、invokevirtual、ldc、ldc_w、ldc2_w、multianewarray、new、putfield
如何调用?<br>按顺序找,调用最后出现的那个,这也就是为什么子类重写了父类中的方法,调用的是子类中的原因所在
模板解释器原理<br>1.call_stub<br>2.entry_point
执行字节码案例<br><br>Java代码<br>public class Test_1 {<br><br> public static void main(String[] args) {<br> int a = 10;<br> }<br>}<br><br>生成的字节码指令<br>0 bipush 10<br>2 istore_1<br>3 return<br>
1.第一步,找到main方法
再点进去你会发现,它是一个接口实现,并且还在jni.h当中,定位不到方法实现中
我们需要jni+下划线+方法名。到jni.cpp里面去找
2.调用jni_invoke_static
3.调用call_stub<br><br>call_stub生成的代码长什么样?<br>哪里生成的?stubGenerator_x86_64.cpp#generate_initial() -> generate_all_stub
StubRoutines::initialize1()
StubGenerator_generator
generate_call_stub里面的 __ enter()做的就是在开启堆栈
解决了什么问题?<br>虚拟机栈在哪里?main方法其实就是call_stub调的,,<br>需要准备调用环境,call_stub是一个函数指针,开辟堆栈<br>call_stub在调用main方法的时候,有两个步骤,第一个是生成call_stub,第二个执行entry_point,<br>然后才会调用到main方法当中去<br><br>call_stub做的事情<br>需要创建自己的栈也就是OS中的栈,然后再创建虚拟机栈。虚拟机栈式寄生在call_stub的栈中,为什么要这样做?<br>因为硬编码编织纪要支持push这样的汇编指令,又要支持虚拟机栈,很矛盾的点,又要支持push汇编指令又要用到虚拟机栈,怎么解决呢?<br>两种方案<br>1.寄生。虚拟机栈寄生在call_stub的栈中,在call_stub的下面拉伸一段,96字节<br>2.移花接木。 也是自实现的JVM版本中的方式,两个虚拟机栈,怎么来回切换呢?如果说在OS栈中,需要用到虚拟机栈,把rsp/esp,切到虚拟机栈,然后用完之后,再切回到OS栈中。切栈
call_stub做了两件事情<br>1.创建堆栈<br>2.进入entry_point
怎么进入的entry_point呢?<br>将Java方法放入到了rbx寄存器当中,然后执行
entry_point什么时候生成的呢?<br>JVM启动的时候,在init_globals()里面的时候生成->interpreter_init()->TemplateInterpreter::initialize()
普通的Java方法定义成zerolocals<br>method_entry(zeroloclas)<br>method_entry(zerolocals_synchronized) 带了synchronized修饰
4.method_entry(zerolocals)<br>Interpreter::_entry_table[Interpreter::kind] = generate_method_entry(Interpreter::kind);<br>return ig_this->generate_normal_entry(synchronized);
return ig_this->generate_normal_entry(synchronized);
生成路径<br>interpreter_init()->
Interpreter继承了CppInterpreter和TemplateInterpreter,我们点进TemplateInterpreter看它的initialize()方法
这里进行了TemplateTable初始化,以及InterpreterGenerator的构造方法
我们点进去看它在x86_64下的构造方法里面做了什么
调用了generate_all()
上面的代码点过去不知道为什么跳转到的式cppInterpreter,正确的式应该是跳到templateInterpreter.cpp
5.__ dispatch_next(vtos);<br>def(Bytecodes::_bipush , ubcp|____|____|____, vtos, itos, bipush , _ );<br>
6.void TemplateTable::bipush() {<br> transition(vtos, itos);<br>// 将bipush后面的操作数赋值给rax<br>__ load_signed_byte(rax, at_bcp(1));<br>}
0 条评论
下一页