首页
技术小册
AIGC
面试刷题
技术文章
MAGENTO
云计算
视频课程
源码下载
PDF书籍
「涨薪秘籍」
登录
注册
01 | Java代码是怎么运行的?
02 | Java的基本类型
03 | Java虚拟机是如何加载Java类的?
04 | JVM是如何执行方法调用的?(上)
05 | JVM是如何执行方法调用的?(下)
06 | JVM是如何处理异常的?
07 | JVM是如何实现反射的?
08 | JVM是怎么实现invokedynamic的?(上)
09 | JVM是怎么实现invokedynamic的?(下)
10 | Java对象的内存布局
11 | 垃圾回收(上)
12 | 垃圾回收(下)
13 | Java内存模型
14 | Java虚拟机是怎么实现synchronized的?
15 | Java语法糖与Java编译器
16 | 即时编译(上)
17 | 即时编译(下)
18 | 即时编译器的中间表达形式
19 | Java字节码(基础篇)
20 | 方法内联(上)
21 | 方法内联(下)
22 | HotSpot虚拟机的intrinsic
23 | 逃逸分析
24 | 字段访问相关优化
25 | 循环优化
26 | 向量化
27 | 注解处理器
28 | 基准测试框架JMH(上)
29 | 基准测试框架JMH(下)
30 | Java虚拟机的监控及诊断工具(命令行篇)
31 | Java虚拟机的监控及诊断工具(GUI篇)
32 | JNI的运行机制
33 | Java Agent与字节码注入
34 | Graal:用Java编译Java
35 | Truffle:语言实现框架
36 | SubstrateVM:AOT编译框架
当前位置:
首页>>
技术小册>>
深入拆解 Java 虚拟机
小册名称:深入拆解 Java 虚拟机
### 章节 32 | JNI的运行机制 在Java的广阔生态系统中,Java Native Interface(JNI)扮演着连接Java与本地应用程序(如C、C++编写的程序)的桥梁角色。它允许Java代码运行期间调用其他语言编写的代码,以及从这些代码中接收回调,极大地扩展了Java程序的功能和性能边界。本章节将深入剖析JNI的运行机制,包括其设计原理、关键概念、使用流程、性能考量以及常见陷阱与解决方案。 #### 32.1 JNI概述 JNI是Java平台的一部分,由Oracle(及之前的Sun Microsystems)设计并维护,旨在促进Java代码与本地应用程序(主要是C和C++)之间的互操作性。它提供了一组API,这些API允许Java代码访问本地应用程序的接口,并与之交互。JNI的引入,使得Java程序能够利用现有的本地库,执行那些由于性能、系统级访问或特定硬件支持等原因而难以在纯Java环境中实现的任务。 #### 32.2 JNI的设计原理 JNI的设计基于几个核心原则: 1. **抽象与封装**:JNI提供了一层抽象,隐藏了本地代码实现的复杂性,使Java代码能够以一种相对简单和一致的方式与本地代码交互。 2. **类型安全**:尽管JNI本身并不强制执行类型检查(这是由C/C++的弱类型特性决定的),但它通过严格的签名匹配机制来确保方法调用的正确性,从而在一定程度上维护了类型安全。 3. **性能优化**:通过允许直接调用本地代码,JNI能够绕过Java虚拟机(JVM)的一些性能开销,尤其是在处理大量计算或需要直接硬件访问的场景中。 4. **可扩展性**:JNI的灵活性使得Java程序能够轻松地集成新的或现有的技术,从而保持Java平台的可扩展性和适应性。 #### 32.3 JNI的关键概念 理解JNI的运行机制,首先需要掌握以下几个关键概念: - **JNI Env**:每个JNI调用都通过一个名为`JNIEnv`的指针进行,它包含了大量用于与Java世界交互的函数指针。`JNIEnv`在每次本地方法调用时传入,保证了线程安全性。 - **JNI类型签名**:JNI使用特定的字符串格式来描述Java方法的参数和返回类型,这些签名是JNI函数进行方法调用时识别Java方法的重要依据。 - **本地方法**:被声明为`native`的Java方法,其实现位于本地代码中。JNI通过特定的机制(如注册本地方法)将这些方法与Java中的声明关联起来。 - **本地引用与全局引用**:JNI提供了不同类型的引用来管理Java对象,包括局部引用(自动回收)和全局引用(需要手动管理)。正确管理这些引用对于防止内存泄漏至关重要。 #### 32.4 JNI的使用流程 使用JNI的基本流程包括以下几个步骤: 1. **声明native方法**:在Java类中,使用`native`关键字声明需要本地实现的方法。 2. **生成头文件**:使用`javac`编译Java类,并使用`javah`(或在Java 10及以后版本中,通过`javac -h`)生成包含native方法声明的C/C++头文件。 3. **实现native方法**:在C/C++代码中实现这些native方法,通过`JNIEnv`和`jclass`等参数与Java世界交互。 4. **编译并链接**:将C/C++代码编译成动态链接库(如.dll、.so文件),并确保Java程序能够找到并加载这个库。 5. **加载库并调用native方法**:在Java代码中,使用`System.loadLibrary`或`System.load`方法加载动态链接库,然后像调用普通Java方法一样调用native方法。 #### 32.5 性能考量 虽然JNI提供了强大的功能,但它也带来了性能上的考量: - **JNI调用开销**:每次JNI调用都伴随着从Java到本地代码的上下文切换,这可能会引入显著的性能开销。 - **类型匹配与检查**:JNI调用时,JVM需要验证参数类型并匹配方法签名,这也可能增加额外的性能负担。 - **内存管理复杂性**:JNI中的内存管理需要开发者手动处理,错误的管理可能导致内存泄漏或程序崩溃。 #### 32.6 常见陷阱与解决方案 在使用JNI时,开发者可能会遇到一些常见的陷阱,以下是一些典型的例子及其解决方案: - **内存泄漏**:通过及时释放全局引用和局部引用,避免在本地代码中持有不再需要的Java对象引用。 - **线程安全问题**:JNI代码需要与Java代码共享JVM的线程模型,确保在多线程环境下正确地同步访问共享资源。 - **异常处理**:本地代码中的异常处理需要特别注意,因为JNI并不直接支持Java的异常机制。通常,需要通过设置错误码或抛出特定异常来通知Java层。 - **平台依赖性**:JNI代码通常依赖于特定的操作系统或硬件平台,这增加了代码移植和维护的难度。通过抽象和封装平台相关代码,可以降低这种依赖性。 #### 32.7 结论 JNI作为Java与本地代码之间的桥梁,为Java程序提供了强大的扩展能力。然而,其复杂性和潜在的性能开销也要求开发者在使用时谨慎行事。通过深入理解JNI的运行机制、遵循最佳实践,并充分利用现代工具和框架(如JNI Checker、JNA等),开发者可以有效地利用JNI来构建高效、可维护的跨平台应用程序。在未来的发展中,随着Java平台的不断演进和新技术的不断涌现,JNI的作用和地位也将继续得到巩固和提升。
上一篇:
31 | Java虚拟机的监控及诊断工具(GUI篇)
下一篇:
33 | Java Agent与字节码注入
该分类下的相关小册推荐:
SpringBoot合辑-高级篇
Java语言基础6-面向对象高级
Java语言基础14-枚举和注解
Mybatis合辑1-Mybatis基础入门
Java语言基础9-常用API和常见算法
Java语言基础4-数组详解
Mybatis合辑3-Mybatis动态SQL
Mybatis合辑5-注解、扩展、SQL构建
Java语言基础13-类的加载和反射
Java必知必会-Maven高级
Java语言基础2-运算符
Mybatis合辑4-Mybatis缓存机制