问题:
Java与C/C++能够相互调用的本质原因是因为JVM是由C/C++写的吗?
回答:
Java 与 C/C++ 能够相互调用 并不是因为 JVM 是用 C/C++ 写的。
JVM 用什么语言实现,只是“实现层面的便利”,但绝不是本质原因。
下面从本质原因、实现原因、可能的误解三方面解释清楚。
一、Java 与 C/C++ 能互相调用的本质原因
本质原因只有一个:Java 语言规范定义了一套跨语言接口——JNI(Java Native Interface)。
换句话说:
Java ↔ Native 的互操作能力来自 JNI 这个标准,而不是来自 JVM 是用 C/C++ 写的。
只要某种 JVM 实现了 JNI 规范,那么:
- Java 代码就可以按规范调用 native 函数;
- Native 代码也可以按规范回调 Java 方法;
至于 JVM 是用 C/C++、Rust、Go、汇编还是别的语言实现,都不影响 JNI 的存在。
二、JVM 用 C/C++ 实现只是“实现层面的便利条件”
虽然 JVM 常用 C/C++ 实现(如 HotSpot、ART),但:
- 即便 JVM 用 Rust 写,也必须实现 JNI;
- 即便 JVM 用 Go 写,也一样可以与 C/C++ 交互;
- JVM 内部如何实现,与 Java 能否调用 C/C++ 没有直接必然关系。
JVM 用 C/C++ 实现的原因包括:
- 更容易与底层 OS、CPU、内存模型打交道;
- 性能好、可移植性强;
- 历史原因(JVM 最早诞生于 1990s,当年系统级开发主要语言就是 C++)。
但这只是“实现选择”,不是“语言互调的根因”。
三、如果 JVM 用别的语言实现,Java 与 C/C++ 还能互调吗?
完全能。
例如:
- 有些 hobby JVM 是用 Python 写的,仍然实现 JNI;
- 有些研究型 JVM 是用 Rust 写的,也通过 FFI 实现 JNI;
- 甚至可以用 C# 来写 JVM,同样可以实现 JNI。
也就是说:
只要 JVM 实现 JNI 规范,Java 就能调用 C/C++。
跟 JVM 自己用什么语言写毫无必然关系。
四、为什么很多人误以为“因为 JVM 用 C/C++ 写,所以能互调”?
这是因为两件事情同时为真,但因果关系并不存在:
- JVM 大多数确实用 C/C++ 写的。
- Java 能通过 JNI 调用 C/C++。
于是很多人“误把相关性当成因果性”。
真实关系是:
- JVM 用 C/C++ 编写 → JNI 的实现更容易
- 但 JNI 的设计 → 才是 Java ↔ C/C++ 互调的根本
五、一句话总结(重点)
Java 与 C/C++ 能互相调用的根本原因是“JNI 规范”,
而不是“JVM 是用 C/C++ 写的”。
JVM 的实现语言只是让“实现 JNI”更方便,但不决定是否能互调。
那么JNI规范到底定义了什么?是如何实现互相调用的?
一、JNI 规范究竟定义了什么?
JNI(Java Native Interface)是 Java 语言规范的一部分,不是 JVM 的内部实现细节。
它明确规定了:
-
Java 如何声明一个 native 方法
public native int add(int a, int b);
JNI 明确约定:
- 这个方法不会由 Java 实现,而是由 外部语言(C/C++)实现。
- JVM 必须提供机制让 Java 能找到并调用这个 native 函数。
-
C/C++ 函数应该是什么样子的
JNI 定义了函数签名规则,例如静态注册:
JNIEXPORT jint JNICALL
Java_com_example_Test_add(JNIEnv* env, jobject thiz, jint a, jint b)
JNI 规范决定:
- 怎么命名;
- 参数如何传递;
- 类型如何映射;
- 如何通过
JNIEnv*访问 Java 对象。
这不是 JVM 的“选择”,而是规范要求。
-
JVM 必须暴露哪些 API 给 native 使用
例如:
(*env)->FindClass(*env)->CallVoidMethod(*env)->NewStringUTF(*env)->GetIntArrayElements
这些 API 全部写在 jni.h 中,是 标准化接口。
只要某个 JVM 实现了这些 API,Java 与任何 native 语言都能交互。
-
引用、对象、数组、异常、线程……如何跨语言管理
例如:
- Native 如何创建 Java 对象
- Native 如何抛出 Java 异常
- Native 如何接管线程(AttachCurrentThread)
- 如何避免 GC 回收 Java 对象(GlobalRef)
这些行为全部由 JNI 固定定义。
二、JVM 内部是如何实现“Java ↔ Native 打通”的?
不管 JVM 用什么语言写,它必须实现以下能力:
-
能加载外部动态库(.so / .dll)
就像操作系统加载普通 C/C++ 动态库一样。
内部相当于做了:
dlopen("libxxx.so");
这个动作和 JVM 是 C/C++ 实现没有直接关系,任何语言都能做:
- Rust:
libloading - Go:
plugin - Python:
ctypes - C#:
DllImport
所以 JVM 用什么语言写不影响这一点。
-
能根据方法名找到 native 函数入口
JNI 的规则只是告诉 JVM:
当 Java 调用某个 native 方法时,你要去动态库里找对应的函数名。
不管 JVM 是 C++ 写的、Rust 写的还是 Python 写的,只要实现:
- 符号查找
- 函数绑定
- 参数传递
即可,本质就是 FFI(Foreign Function Interface)。
这个能力不依赖 JVM 本身必须用 C++ 实现。
一种信息传递的方式,协议。读写可以相互遵守就好了。
-
能暴露 JNIEnv(操作 Java 世界的能力)
JVM 必须给 native 层传入一个结构体(JNIEnv*),里面存放:
- 调用 Java 方法的入口
- 创建 Java 对象的入口
- 数组、字符串访问入口
- 异常处理入口
这是 API 级别的约束,不是语言级约束。
一个 Rust 写的 JVM 也完全可以暴露这样的 API。
-
能处理不同语言之间的内存模型与线程 -- 关键!
JNI 的关键任务之一是屏蔽 Java 和 native 之间的差异:
- Java 有 GC
- Native 没 GC
- Java 对象布局虚拟机内部可变(ART 和 HotSpot 都不同)
- Native 指针是裸指针
JNI 的意义就是把所有复杂性隐藏在虚拟机内部,让开发者只需要用有限的 API 交互。
三、为什么说“任何语言都能与 Java 互操作”?
因为 JNI 是一个语言无关的规范。-- 一种规范,一种协议,并不绑定语言
只要一个语言具备 FFI 能力(可以加载动态库、调用 C 函数),它就能调用 Java。
例如:
- Go → 使用 cgo 访问 JNI
- Python → ctypes 调 JNI
- Rust → bindgen + jni crate
- C# → P/Invoke
- Swift → SwiftFFI
- 汇编 → 手写调用 JNI
这说明:
Java 与 native 的互调本质上是 FFI,而不是“JVM 用 C 写的”产生的。
四、那么 JVM 用 C/C++ 编写与 JNI 有什么关系?
关系只有:
C/C++ 让实现 JNI 更容易,但不决定是否能实现 JNI。
举例:
- JVM 要加载
.so,C/C++ 有成熟 API(dlopen) - JVM 要管理线程,C/C++ 有 pthread
- JVM 要处理内存,C/C++ 直接访问 OS 内存管理
换句话说:
JVM 用 C/C++ 写,让 JNI 的实现更方便,但不是 Java 能否调用 C/C++ 的根本原因。
就像:
- 浏览器大多用 C++ 写
- 但网页能执行 JS,不是因为浏览器是 C++ 写的,而是因为浏览器实现了 JS 引擎规范
同理:
- JVM 恰好用 C++ 写
- Java 能调 Native,是因为 JVM 实现了 JNI 规范,而不是因为 JVM 用 C++ 写的