前言
看 JDK 源码时,很多核心方法点进去只有一个分号,方法体直接消失了。Java 到底怎么跨语言调用 C/C++,本地方法栈又是如何管理这些调用的?本文用真实链路带你拆解透彻。
文章目录
-
- 前言
- 一、点进源码只有一个分号,实现代码去哪了
- 二、为什么Java必须留一扇通往C语言的后门
-
- [2.1 Java代码无法直接操作底层硬件](#2.1 Java代码无法直接操作底层硬件)
- [2.2 极致的执行性能与底层计算](#2.2 极致的执行性能与底层计算)
- [2.3 遗留系统与现有资产的复用](#2.3 遗留系统与现有资产的复用)
- 三、本地方法栈的执行机制与HotSpot的合二为一
-
- [3.1 本地方法栈的角色与分工](#3.1 本地方法栈的角色与分工)
- [3.2 HotSpot为什么把两个栈合二为一了](#3.2 HotSpot为什么把两个栈合二为一了)
- 四、本地方法报错会怎样,栈溢出与进程崩溃
-
- [4.1 本地方法栈的两类标准内存异常](#4.1 本地方法栈的两类标准内存异常)
- [4.2 JNI 调用引发进程崩溃的破坏力](#4.2 JNI 调用引发进程崩溃的破坏力)
- 写在最后
一、点进源码只有一个分号,实现代码去哪了
刚开始翻看 JDK 源码的同学,大概率都遇到过这种让人摸不着头脑的场景:
你想看 Thread.start() 底层到底是怎么创建系统线程的,点进去一路追,最后在 Thread.java 里看到了这样一行代码:
java
package com.crayontech.jvm;
public class NativeMethodDemo {
// 只有一个 native 修饰符,后面直接跟着分号,连大括号都没有
private native void start0();
}
或者你想看看每个 Java 对象都有的 hashCode() 是怎么算出来的,在 Object.java 里看到的同样只有一行:
java
public native int hashCode();
方法声明后面没有大括号,直接用一个分号收尾。
初学者往往会纳闷:实现代码到底去哪了?难道 JVM 里藏着什么见不得人的黑魔法?
其实一点也不神秘。这行代码上的关键在于 native 关键字。
一旦一个方法被声明为 native,就意味着这个方法是一个本地方法 (Native Method)。它的签名在 Java 里定义,但真正的逻辑实现完全不归 Java 管,全由底层的 C、C++ 或汇编语言写好,提前编译成了操作系统能够直接识别的动态链接库(在 Linux 下是 .so 文件,在 Windows 下是 .dll 文件,在 macOS 下是 .dylib 文件)。
当 Java 程序运行时,虚拟机通过 JNI(Java Native Interface,Java 本地接口)去加载这些动态链接库,并建立 Java 方法与 C/C++ 函数之间的映射关系。
#mermaid-svg-LZKIQikWXnoRVE1M{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-LZKIQikWXnoRVE1M .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-LZKIQikWXnoRVE1M .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-LZKIQikWXnoRVE1M .error-icon{fill:#552222;}#mermaid-svg-LZKIQikWXnoRVE1M .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-LZKIQikWXnoRVE1M .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-LZKIQikWXnoRVE1M .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-LZKIQikWXnoRVE1M .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-LZKIQikWXnoRVE1M .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-LZKIQikWXnoRVE1M .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-LZKIQikWXnoRVE1M .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-LZKIQikWXnoRVE1M .marker{fill:#333333;stroke:#333333;}#mermaid-svg-LZKIQikWXnoRVE1M .marker.cross{stroke:#333333;}#mermaid-svg-LZKIQikWXnoRVE1M svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-LZKIQikWXnoRVE1M p{margin:0;}#mermaid-svg-LZKIQikWXnoRVE1M .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-LZKIQikWXnoRVE1M .cluster-label text{fill:#333;}#mermaid-svg-LZKIQikWXnoRVE1M .cluster-label span{color:#333;}#mermaid-svg-LZKIQikWXnoRVE1M .cluster-label span p{background-color:transparent;}#mermaid-svg-LZKIQikWXnoRVE1M .label text,#mermaid-svg-LZKIQikWXnoRVE1M span{fill:#333;color:#333;}#mermaid-svg-LZKIQikWXnoRVE1M .node rect,#mermaid-svg-LZKIQikWXnoRVE1M .node circle,#mermaid-svg-LZKIQikWXnoRVE1M .node ellipse,#mermaid-svg-LZKIQikWXnoRVE1M .node polygon,#mermaid-svg-LZKIQikWXnoRVE1M .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-LZKIQikWXnoRVE1M .rough-node .label text,#mermaid-svg-LZKIQikWXnoRVE1M .node .label text,#mermaid-svg-LZKIQikWXnoRVE1M .image-shape .label,#mermaid-svg-LZKIQikWXnoRVE1M .icon-shape .label{text-anchor:middle;}#mermaid-svg-LZKIQikWXnoRVE1M .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-LZKIQikWXnoRVE1M .rough-node .label,#mermaid-svg-LZKIQikWXnoRVE1M .node .label,#mermaid-svg-LZKIQikWXnoRVE1M .image-shape .label,#mermaid-svg-LZKIQikWXnoRVE1M .icon-shape .label{text-align:center;}#mermaid-svg-LZKIQikWXnoRVE1M .node.clickable{cursor:pointer;}#mermaid-svg-LZKIQikWXnoRVE1M .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-LZKIQikWXnoRVE1M .arrowheadPath{fill:#333333;}#mermaid-svg-LZKIQikWXnoRVE1M .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-LZKIQikWXnoRVE1M .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-LZKIQikWXnoRVE1M .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LZKIQikWXnoRVE1M .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-LZKIQikWXnoRVE1M .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LZKIQikWXnoRVE1M .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-LZKIQikWXnoRVE1M .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-LZKIQikWXnoRVE1M .cluster text{fill:#333;}#mermaid-svg-LZKIQikWXnoRVE1M .cluster span{color:#333;}#mermaid-svg-LZKIQikWXnoRVE1M div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-LZKIQikWXnoRVE1M .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-LZKIQikWXnoRVE1M rect.text{fill:none;stroke-width:0;}#mermaid-svg-LZKIQikWXnoRVE1M .icon-shape,#mermaid-svg-LZKIQikWXnoRVE1M .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LZKIQikWXnoRVE1M .icon-shape p,#mermaid-svg-LZKIQikWXnoRVE1M .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-LZKIQikWXnoRVE1M .icon-shape .label rect,#mermaid-svg-LZKIQikWXnoRVE1M .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LZKIQikWXnoRVE1M .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-LZKIQikWXnoRVE1M .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-LZKIQikWXnoRVE1M :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 操作系统原生运行时(Native OS)
Java 运行时环境(JVM 沙箱)
动态加载与符号查找
Thread.start() / Object.hashCode()
native 方法声明
(仅有方法签名,分号结尾)
JNI 本地接口桥接层
(Java Native Interface)
动态链接库 (.so / .dll / .dylib)
C/C++ 实现函数
(JVM_StartThread / ObjectSynchronizer)
操作系统的底层系统调用
(pthread_create / 硬件时钟 / 内存总线)
native 方法就像是 Java 打开的一扇通往原生底层世界的暗门。
二、为什么Java必须留一扇通往C语言的后门
Java 诞生之初主打的旗号是"一次编写,到处运行",依靠虚拟机沙箱隔绝了操作系统的差异。既然要跨平台和安全,为什么还要在语言规范里特意保留 native 这种打破沙箱的机制?
原因很现实,主要集中在三个纯 Java 根本绕不开的硬骨头问题上:
2.1 Java代码无法直接操作底层硬件
Java 代码运行在 JVM 抽象出的虚拟指令集之上,它是没有权限直接操作 CPU 寄存器、操作显卡驱动或者向物理内存指定地址写入数据的。
但是,现代操作系统管理着进程、物理线程、虚拟内存、网络网卡与磁盘 I/O。当你在 Java 里调用 new Thread().start() 时,JVM 必须向操作系统内核发起系统调用(例如 Linux 上的 clone() 或 pthread_create()),让操作系统调度器切出一段真正的内核线程。
Java 虚拟机自己是用 C++ 写的,它必须通过本地方法才能拿到操作系统的原生句柄,完成线程创建、文件读写、套接字通信以及控制台输出。
2.2 极致的执行性能与底层计算
在密码学哈希、音视频编解码、3D 图形渲染、大矩阵数学计算以及高并发原子操作等场景下,即便是拥有强大 JIT 编译器的 Java,也很难做到像 C/C++ 配合特定 CPU 架构的 SIMD(单指令多数据流)向量指令那样贴近硬件极限。
通过 JNI 调用现成且经过数十年工业级优化的 C/C++ 基础库(比如 OpenCV、FFmpeg、BLAS 等),Java 生态既能享受到上层高生产力,又能直接复用底层的极速算力。
2.3 遗留系统与现有资产的复用
在 Java 面世之前,各大金融机构、工业软件和嵌入式设备里已经积累了海量经过严苛验证的 C/C++ 代码库。如果把这些老系统全部用 Java 重新写一遍,成本巨大不说,还容易引入未知的风险隐患。
有了 JNI 和本地方法,Java 就可以直接当作一个胶水语言,在上层编写清晰优美的业务逻辑,底层照样无缝驱动老旧的 C 库。
#mermaid-svg-9hqYLQQHAO1MYVw3{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-9hqYLQQHAO1MYVw3 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-9hqYLQQHAO1MYVw3 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-9hqYLQQHAO1MYVw3 .error-icon{fill:#552222;}#mermaid-svg-9hqYLQQHAO1MYVw3 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-9hqYLQQHAO1MYVw3 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-9hqYLQQHAO1MYVw3 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-9hqYLQQHAO1MYVw3 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-9hqYLQQHAO1MYVw3 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-9hqYLQQHAO1MYVw3 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-9hqYLQQHAO1MYVw3 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-9hqYLQQHAO1MYVw3 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-9hqYLQQHAO1MYVw3 .marker.cross{stroke:#333333;}#mermaid-svg-9hqYLQQHAO1MYVw3 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-9hqYLQQHAO1MYVw3 p{margin:0;}#mermaid-svg-9hqYLQQHAO1MYVw3 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-9hqYLQQHAO1MYVw3 .cluster-label text{fill:#333;}#mermaid-svg-9hqYLQQHAO1MYVw3 .cluster-label span{color:#333;}#mermaid-svg-9hqYLQQHAO1MYVw3 .cluster-label span p{background-color:transparent;}#mermaid-svg-9hqYLQQHAO1MYVw3 .label text,#mermaid-svg-9hqYLQQHAO1MYVw3 span{fill:#333;color:#333;}#mermaid-svg-9hqYLQQHAO1MYVw3 .node rect,#mermaid-svg-9hqYLQQHAO1MYVw3 .node circle,#mermaid-svg-9hqYLQQHAO1MYVw3 .node ellipse,#mermaid-svg-9hqYLQQHAO1MYVw3 .node polygon,#mermaid-svg-9hqYLQQHAO1MYVw3 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-9hqYLQQHAO1MYVw3 .rough-node .label text,#mermaid-svg-9hqYLQQHAO1MYVw3 .node .label text,#mermaid-svg-9hqYLQQHAO1MYVw3 .image-shape .label,#mermaid-svg-9hqYLQQHAO1MYVw3 .icon-shape .label{text-anchor:middle;}#mermaid-svg-9hqYLQQHAO1MYVw3 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-9hqYLQQHAO1MYVw3 .rough-node .label,#mermaid-svg-9hqYLQQHAO1MYVw3 .node .label,#mermaid-svg-9hqYLQQHAO1MYVw3 .image-shape .label,#mermaid-svg-9hqYLQQHAO1MYVw3 .icon-shape .label{text-align:center;}#mermaid-svg-9hqYLQQHAO1MYVw3 .node.clickable{cursor:pointer;}#mermaid-svg-9hqYLQQHAO1MYVw3 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-9hqYLQQHAO1MYVw3 .arrowheadPath{fill:#333333;}#mermaid-svg-9hqYLQQHAO1MYVw3 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-9hqYLQQHAO1MYVw3 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-9hqYLQQHAO1MYVw3 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-9hqYLQQHAO1MYVw3 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-9hqYLQQHAO1MYVw3 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-9hqYLQQHAO1MYVw3 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-9hqYLQQHAO1MYVw3 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-9hqYLQQHAO1MYVw3 .cluster text{fill:#333;}#mermaid-svg-9hqYLQQHAO1MYVw3 .cluster span{color:#333;}#mermaid-svg-9hqYLQQHAO1MYVw3 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-9hqYLQQHAO1MYVw3 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-9hqYLQQHAO1MYVw3 rect.text{fill:none;stroke-width:0;}#mermaid-svg-9hqYLQQHAO1MYVw3 .icon-shape,#mermaid-svg-9hqYLQQHAO1MYVw3 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-9hqYLQQHAO1MYVw3 .icon-shape p,#mermaid-svg-9hqYLQQHAO1MYVw3 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-9hqYLQQHAO1MYVw3 .icon-shape .label rect,#mermaid-svg-9hqYLQQHAO1MYVw3 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-9hqYLQQHAO1MYVw3 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-9hqYLQQHAO1MYVw3 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-9hqYLQQHAO1MYVw3 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 典型的 JDK 内部核心 Native 场景
Java 依赖 Native 方法的三大核心诉求
系统内核交互
(创建物理线程、直接物理内存分配、硬件时钟)
计算性能极限
(SIMD 向量加速、音视频解码、高频硬件指令)
历史代码复用
(复用成熟的 C/C++ 工业软件与驱动接口)
Thread.start0() -> 操作系统创建线程
Unsafe.allocateMemory() -> 直接向 OS 申请堆外内存
Math.sin() / StrictMath -> 直接调用底层 FPU 浮点运算器
CRC32 / AES 硬件加速指令
AWT / Swing 窗口绘制 -> 底层 X11 / Win32 / Cocoa 接口
三、本地方法栈的执行机制与HotSpot的合二为一
理解了本地方法,接下来就能看懂本地方法栈(Native Method Stack)到底是用来干什么的了。
3.1 本地方法栈的角色与分工
前面我们拆解过 Java 虚拟机栈:每当线程调用一个 Java 方法时,JVM 就会在虚拟机栈里压入一个栈帧,用来存放这个 Java 方法的局部变量表、操作数栈、动态链接和方法出口。
那么,当线程执行到一个 native 方法时会发生什么?
由于本地方法是用 C/C++ 写的,根本不包含 Java 字节码,操作数栈和局部变量表的结构自然也就不适用了。
这时候,就需要一块专门的内存空间来支撑 C/C++ 函数的执行------这就是本地方法栈。
本地方法栈同样是线程私有的,生命周期随着线程创建而产生,随着线程销毁而终结。当一个线程调用本地方法时,它的执行上下文就从 Java 虚拟机的掌控区切换到了底层 C 运行时环境:
- 本地方法栈负责维护 C/C++ 函数调用时需要的栈帧(包括 C 函数的参数、局部变量、返回地址以及寄存器备份);
- C 语言里的指针寻址、内存操作都在这个栈空间和系统内存中展开;
- 如果这个本地方法执行过程中又反向回调了 Java 方法,执行上下文还会从本地方法栈再次切回到 Java 虚拟机栈。
底层 C/C++ 动态库 本地方法栈 Java 虚拟机栈 工作线程 底层 C/C++ 动态库 本地方法栈 Java 虚拟机栈 工作线程 #mermaid-svg-yP0iNxkjlZ9VGNUc{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-yP0iNxkjlZ9VGNUc .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-yP0iNxkjlZ9VGNUc .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-yP0iNxkjlZ9VGNUc .error-icon{fill:#552222;}#mermaid-svg-yP0iNxkjlZ9VGNUc .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-yP0iNxkjlZ9VGNUc .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-yP0iNxkjlZ9VGNUc .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-yP0iNxkjlZ9VGNUc .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-yP0iNxkjlZ9VGNUc .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-yP0iNxkjlZ9VGNUc .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-yP0iNxkjlZ9VGNUc .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-yP0iNxkjlZ9VGNUc .marker{fill:#333333;stroke:#333333;}#mermaid-svg-yP0iNxkjlZ9VGNUc .marker.cross{stroke:#333333;}#mermaid-svg-yP0iNxkjlZ9VGNUc svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-yP0iNxkjlZ9VGNUc p{margin:0;}#mermaid-svg-yP0iNxkjlZ9VGNUc .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-yP0iNxkjlZ9VGNUc text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-yP0iNxkjlZ9VGNUc .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-yP0iNxkjlZ9VGNUc .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-yP0iNxkjlZ9VGNUc .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-yP0iNxkjlZ9VGNUc .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-yP0iNxkjlZ9VGNUc #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-yP0iNxkjlZ9VGNUc .sequenceNumber{fill:white;}#mermaid-svg-yP0iNxkjlZ9VGNUc #sequencenumber{fill:#333;}#mermaid-svg-yP0iNxkjlZ9VGNUc #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-yP0iNxkjlZ9VGNUc .messageText{fill:#333;stroke:none;}#mermaid-svg-yP0iNxkjlZ9VGNUc .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-yP0iNxkjlZ9VGNUc .labelText,#mermaid-svg-yP0iNxkjlZ9VGNUc .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-yP0iNxkjlZ9VGNUc .loopText,#mermaid-svg-yP0iNxkjlZ9VGNUc .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-yP0iNxkjlZ9VGNUc .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-yP0iNxkjlZ9VGNUc .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-yP0iNxkjlZ9VGNUc .noteText,#mermaid-svg-yP0iNxkjlZ9VGNUc .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-yP0iNxkjlZ9VGNUc .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-yP0iNxkjlZ9VGNUc .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-yP0iNxkjlZ9VGNUc .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-yP0iNxkjlZ9VGNUc .actorPopupMenu{position:absolute;}#mermaid-svg-yP0iNxkjlZ9VGNUc .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-yP0iNxkjlZ9VGNUc .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-yP0iNxkjlZ9VGNUc .actor-man circle,#mermaid-svg-yP0iNxkjlZ9VGNUc line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-yP0iNxkjlZ9VGNUc :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 发生执行上下文切换(Java ->> Native) 恢复 Java 运行时上下文 调用业务方法 calculate() ->> 压入 Java 栈帧 1 执行 Java 字节码运算 2 调用 native 方法 start0() 3 进入 JNI 桥接 ->> 压入 Native 栈帧(C 语言上下文) 4 执行 C 函数 JVM_StartThread() 5 底层系统调用完成,返回结果 6 弹出 Native 栈帧 7 弹出 calculate() 栈帧,调用链结束 8
3.2 HotSpot为什么把两个栈合二为一了
在阅读官方文档时,有一个很容易产生认知偏差的地方:
《Java 虚拟机规范》对本地方法栈的具体实现做出了非常宽泛的规定:
- 允许虚拟机实现者自由选择本地方法栈的语言、底层数据结构;
- 如果 JVM 根本不支持调用 native 方法,甚至可以完全不提供本地方法栈;
- 如果支持 native 方法,规范要求在逻辑概念上将 Java 虚拟机栈与本地方法栈区分开来。
在许多经典的虚拟机设计中,这两个栈是各管各的,分别开辟独立的内存连续区域。
但在我们现在最常用、最主流的 HotSpot 虚拟机 中,官方做了一个极其务实的选择:
直接将 Java 虚拟机栈与本地方法栈合二为一!
#mermaid-svg-J89APnd7ZSRm55xf{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-J89APnd7ZSRm55xf .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-J89APnd7ZSRm55xf .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-J89APnd7ZSRm55xf .error-icon{fill:#552222;}#mermaid-svg-J89APnd7ZSRm55xf .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-J89APnd7ZSRm55xf .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-J89APnd7ZSRm55xf .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-J89APnd7ZSRm55xf .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-J89APnd7ZSRm55xf .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-J89APnd7ZSRm55xf .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-J89APnd7ZSRm55xf .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-J89APnd7ZSRm55xf .marker{fill:#333333;stroke:#333333;}#mermaid-svg-J89APnd7ZSRm55xf .marker.cross{stroke:#333333;}#mermaid-svg-J89APnd7ZSRm55xf svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-J89APnd7ZSRm55xf p{margin:0;}#mermaid-svg-J89APnd7ZSRm55xf .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-J89APnd7ZSRm55xf .cluster-label text{fill:#333;}#mermaid-svg-J89APnd7ZSRm55xf .cluster-label span{color:#333;}#mermaid-svg-J89APnd7ZSRm55xf .cluster-label span p{background-color:transparent;}#mermaid-svg-J89APnd7ZSRm55xf .label text,#mermaid-svg-J89APnd7ZSRm55xf span{fill:#333;color:#333;}#mermaid-svg-J89APnd7ZSRm55xf .node rect,#mermaid-svg-J89APnd7ZSRm55xf .node circle,#mermaid-svg-J89APnd7ZSRm55xf .node ellipse,#mermaid-svg-J89APnd7ZSRm55xf .node polygon,#mermaid-svg-J89APnd7ZSRm55xf .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-J89APnd7ZSRm55xf .rough-node .label text,#mermaid-svg-J89APnd7ZSRm55xf .node .label text,#mermaid-svg-J89APnd7ZSRm55xf .image-shape .label,#mermaid-svg-J89APnd7ZSRm55xf .icon-shape .label{text-anchor:middle;}#mermaid-svg-J89APnd7ZSRm55xf .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-J89APnd7ZSRm55xf .rough-node .label,#mermaid-svg-J89APnd7ZSRm55xf .node .label,#mermaid-svg-J89APnd7ZSRm55xf .image-shape .label,#mermaid-svg-J89APnd7ZSRm55xf .icon-shape .label{text-align:center;}#mermaid-svg-J89APnd7ZSRm55xf .node.clickable{cursor:pointer;}#mermaid-svg-J89APnd7ZSRm55xf .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-J89APnd7ZSRm55xf .arrowheadPath{fill:#333333;}#mermaid-svg-J89APnd7ZSRm55xf .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-J89APnd7ZSRm55xf .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-J89APnd7ZSRm55xf .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-J89APnd7ZSRm55xf .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-J89APnd7ZSRm55xf .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-J89APnd7ZSRm55xf .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-J89APnd7ZSRm55xf .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-J89APnd7ZSRm55xf .cluster text{fill:#333;}#mermaid-svg-J89APnd7ZSRm55xf .cluster span{color:#333;}#mermaid-svg-J89APnd7ZSRm55xf div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-J89APnd7ZSRm55xf .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-J89APnd7ZSRm55xf rect.text{fill:none;stroke-width:0;}#mermaid-svg-J89APnd7ZSRm55xf .icon-shape,#mermaid-svg-J89APnd7ZSRm55xf .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-J89APnd7ZSRm55xf .icon-shape p,#mermaid-svg-J89APnd7ZSRm55xf .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-J89APnd7ZSRm55xf .icon-shape .label rect,#mermaid-svg-J89APnd7ZSRm55xf .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-J89APnd7ZSRm55xf .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-J89APnd7ZSRm55xf .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-J89APnd7ZSRm55xf :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} HotSpot 工程优化合并
HotSpot 虚拟机实现(物理统一)
统一栈内的交替栈帧
Java 栈帧:calculate()
Native 栈帧:start0() (JNI 调用)
Java 栈帧:main()
统一线程调用栈(物理上使用同一个 C 原生调用栈)
Java 虚拟机规范(逻辑设计)
Java 虚拟机栈
(仅负责 Java 字节码栈帧)
本地方法栈
(负责 C/C++ 原生栈帧)
为什么 HotSpot 要这么干?
因为 HotSpot 本身就是用 C++ 开发并编译为平台机器码的程序。在操作系统层面,一个物理线程从创建伊始,操作系统就会天然为它分配一个连续的机器调用栈(C Runtime Stack)。
既然底层已经有了一个现成的机器栈,再去额外开辟一块孤立的内存当本地方法栈,既增加了栈指针切换与内存对齐的开销,也加大了内存管理的碎片率。
因此在 HotSpot 源码中:
- 栈空间合二为一:Java 方法栈帧和 Native 方法的 C 栈帧,直接交替存放在操作系统分配给该线程的同一个调用栈上;
- 调优参数合二为一 :HotSpot 中没有专门配置本地方法栈大小的独立参数,我们在启动时配置的
-Xss参数(或者-XX:ThreadStackSize),直接统筹决定了这整根合并栈的容量上限。
四、本地方法报错会怎样,栈溢出与进程崩溃
既然本地方法能像普通方法一样运行,那它在遇到极端异常时,表现会和 Java 方法一样吗?
我们平时调 Java 代码,哪怕栈深度不够了,顶多是主线程抓到一个 StackOverflowError,只要 catch 住,整个进程通常不会死掉。但本地方法栈一旦出事,性质就截然不同了。
4.1 本地方法栈的两类标准内存异常
与虚拟机栈一样,规范中明确定义了本地方法栈可能抛出的两类错误:
- StackOverflowError(栈深度溢出) :
如果本地方法栈采用固定大小,而线程请求的栈容量超过了允许的最大深度,就会抛出StackOverflowError。例如用 JNI 调用的 C 语言函数内部写了一个没有终止条件的死递归,把线程的栈空间耗尽时就会触发。 - OutOfMemoryError(内存耗尽) :
如果本地方法栈支持动态扩展,但在向操作系统申请更多内存扩展栈容量时,操作系统物理内存已经告罄,或者并发创建了太多线程导致操作系统再也分不出新的线程栈空间,JVM 就会抛出OutOfMemoryError。
4.2 JNI 调用引发进程崩溃的破坏力
Java 虚拟机给开发者提供了一个安全沙箱:数组越界有 ArrayIndexOutOfBoundsException,空对象访问有 NullPointerException,哪怕内存泄漏也有 GC 日志和 OOM 堆转储。
但是,只要调用逻辑跨过了 JNI 边界进入 C/C++ 代码,这个安全沙箱就彻底失效了。
来看一个简单的 JNI 示例来感受这种反差:
java
package com.crayontech.jvm;
public class NativeCrashSimulator {
static {
// 加载打包好的底层动态链接库
System.loadLibrary("native_calc");
}
// 声明本地方法
public native int executeNativeCalculation(int[] data);
public static void main(String[] args) {
NativeCrashSimulator simulator = new NativeCrashSimulator();
// 传递一个 null 指针或者错误长度进去
int result = simulator.executeNativeCalculation(null);
System.out.println("计算结果:" + result);
}
}
如果在底层 C 函数的实现中,写代码的人没有做防御性判空,直接拿传进来的野指针解引用写入数据:
c
// 对应的底层 C 代码片段
JNIEXPORT jint JNICALL Java_com_crayontech_jvm_NativeCrashSimulator_executeNativeCalculation
(JNIEnv *env, jobject obj, jintArray array) {
// 如果没有使用 (*env)->GetIntArrayElements 进行安全校验,直接裸指针读写
int *ptr = NULL;
*ptr = 100; // 经典的野指针解引用,引发非法内存访问
return 100;
}
在 Java 代码里,你哪怕在外面套了十层 try ... catch (Throwable t),也绝对拦不住这个错误。
因为程序此时直接触发了操作系统的 段错误 (Linux 下的 SIGSEGV,Windows 下的 EXCEPTION_ACCESS_VIOLATION),这根本不属于 Java 虚拟机能捕获的语言级异常。
操作系统会直接向 JVM 发送致命信号,强制终止当前进程。整个 Java 应用程序会在瞬间直接闪退,控制台只会留下一份类似于 hs_err_pid<pid>.log 的 JVM 致命崩溃日志。
#mermaid-svg-e4WH5XsiwjySLCoH{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-e4WH5XsiwjySLCoH .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-e4WH5XsiwjySLCoH .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-e4WH5XsiwjySLCoH .error-icon{fill:#552222;}#mermaid-svg-e4WH5XsiwjySLCoH .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-e4WH5XsiwjySLCoH .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-e4WH5XsiwjySLCoH .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-e4WH5XsiwjySLCoH .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-e4WH5XsiwjySLCoH .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-e4WH5XsiwjySLCoH .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-e4WH5XsiwjySLCoH .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-e4WH5XsiwjySLCoH .marker{fill:#333333;stroke:#333333;}#mermaid-svg-e4WH5XsiwjySLCoH .marker.cross{stroke:#333333;}#mermaid-svg-e4WH5XsiwjySLCoH svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-e4WH5XsiwjySLCoH p{margin:0;}#mermaid-svg-e4WH5XsiwjySLCoH .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-e4WH5XsiwjySLCoH .cluster-label text{fill:#333;}#mermaid-svg-e4WH5XsiwjySLCoH .cluster-label span{color:#333;}#mermaid-svg-e4WH5XsiwjySLCoH .cluster-label span p{background-color:transparent;}#mermaid-svg-e4WH5XsiwjySLCoH .label text,#mermaid-svg-e4WH5XsiwjySLCoH span{fill:#333;color:#333;}#mermaid-svg-e4WH5XsiwjySLCoH .node rect,#mermaid-svg-e4WH5XsiwjySLCoH .node circle,#mermaid-svg-e4WH5XsiwjySLCoH .node ellipse,#mermaid-svg-e4WH5XsiwjySLCoH .node polygon,#mermaid-svg-e4WH5XsiwjySLCoH .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-e4WH5XsiwjySLCoH .rough-node .label text,#mermaid-svg-e4WH5XsiwjySLCoH .node .label text,#mermaid-svg-e4WH5XsiwjySLCoH .image-shape .label,#mermaid-svg-e4WH5XsiwjySLCoH .icon-shape .label{text-anchor:middle;}#mermaid-svg-e4WH5XsiwjySLCoH .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-e4WH5XsiwjySLCoH .rough-node .label,#mermaid-svg-e4WH5XsiwjySLCoH .node .label,#mermaid-svg-e4WH5XsiwjySLCoH .image-shape .label,#mermaid-svg-e4WH5XsiwjySLCoH .icon-shape .label{text-align:center;}#mermaid-svg-e4WH5XsiwjySLCoH .node.clickable{cursor:pointer;}#mermaid-svg-e4WH5XsiwjySLCoH .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-e4WH5XsiwjySLCoH .arrowheadPath{fill:#333333;}#mermaid-svg-e4WH5XsiwjySLCoH .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-e4WH5XsiwjySLCoH .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-e4WH5XsiwjySLCoH .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-e4WH5XsiwjySLCoH .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-e4WH5XsiwjySLCoH .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-e4WH5XsiwjySLCoH .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-e4WH5XsiwjySLCoH .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-e4WH5XsiwjySLCoH .cluster text{fill:#333;}#mermaid-svg-e4WH5XsiwjySLCoH .cluster span{color:#333;}#mermaid-svg-e4WH5XsiwjySLCoH div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-e4WH5XsiwjySLCoH .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-e4WH5XsiwjySLCoH rect.text{fill:none;stroke-width:0;}#mermaid-svg-e4WH5XsiwjySLCoH .icon-shape,#mermaid-svg-e4WH5XsiwjySLCoH .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-e4WH5XsiwjySLCoH .icon-shape p,#mermaid-svg-e4WH5XsiwjySLCoH .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-e4WH5XsiwjySLCoH .icon-shape .label rect,#mermaid-svg-e4WH5XsiwjySLCoH .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-e4WH5XsiwjySLCoH .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-e4WH5XsiwjySLCoH .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-e4WH5XsiwjySLCoH :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} JNI 本地方法致命错误
C 代码野指针 / 缓冲区溢出 / 内存越界
操作系统捕获非法物理地址访问,抛出 SIGSEGV 信号
生成 hs_err_pid 崩溃日志,JVM 进程被操作系统直接杀死
普通 Java 方法异常处理
Java 代码逻辑错误 / 空指针 / 递归越界
进入 JVM 异常捕获机制(try-catch)
打印堆栈信息,工作线程可恢复,进程安全运行
这也是为什么在日常企业级业务开发中,主流技术架构很少推荐业务开发人员自行手写 JNI 代码。一旦底层 C 代码出现哪怕一处内存泄漏或缓冲区溢出,毁掉的就绝不止单个线程,整个容器或者微服务节点都会跟着一起当场崩溃。
写在最后
本地方法栈在日常业务开发中存在感极低,但它却是 JVM 连接真实物理世界的生命通道。
从底层的操作系统线程调度、高精度时间戳获取,到堆外物理内存管理,Java 之所以能在提供极致安全开发体验的同时保持强大的底层控制力,全靠本地方法与本地方法栈在幕后默默支撑。
搞懂了本地方法栈,下一次在源码里看到只有分号的 native 方法时,你就知道它背后的 C 语言齿轮是如何转动的了。
如果这篇拆解对你理解 JVM 运行时数据区有所启发,记得点个关注,追更本系列的后续深度拆解!