Skip to content

本地方法接口与本地方法栈

为什么 JVM 需要 native 方法

Java 不是万能的。有些功能需要直接调用操作系统底层能力,比如:

  • 获取系统时间:System.currentTimeMillis() 底层调用的是 OS 的 gettimeofday()
  • 读取文件描述符:操作系统的文件操作
  • 本地内存操作:ByteBuffer.allocateDirect() 背后是 OS 的本地内存分配
  • 调用 C/C++ 库:与遗留系统交互

这些场景下,Java 需要借助 JNI(Java Native Interface) 调用本地代码(通常用 C 或 C++ 编写)。

JNI:Java 与本地代码的桥梁

JNI 是 Java 官方定义的接口标准,规定了 Java 代码如何调用本地方法,以及本地方法如何访问 Java 对象。

声明 native 方法

java
public class NativeDemo {
    // 声明 native 方法
    public native void sayHello();

    // 加载包含 native 实现的标准库
    static {
        System.loadLibrary("nativeimpl");  // 加载 libnativeimpl.so(或 .dll)
    }

    public static void main(String[] args) {
        new NativeDemo().sayHello();
    }
}

native 方法的实现(C 语言)

c
// nativeimpl.c
#include <stdio.h>
#include <jni.h>  // JNI 头文件

JNIEXPORT void JNICALL Java_NativeDemo_sayHello(JNIEnv *env, jobject this) {
    printf("Hello from C!\n");
}

native 方法的调用过程

Java 层:new NativeDemo().sayHello()


JNI 查找本地库中的实现函数
Java_NativeDemo_sayHello


执行 C 代码:printf("Hello from C!")

本地方法栈:native 方法的「虚拟机栈」

和 Java 虚拟机栈一样,JVM 为 native 方法提供了本地方法栈(Native Method Stack)

本地方法栈 vs Java 虚拟机栈

维度Java 虚拟机栈本地方法栈
服务对象Java 方法native 方法
存储内容栈帧(局部变量表、操作数栈等)栈帧(C/C++ 调用栈)
语言JVM 字节码指令本地机器码
生命周期与线程同步与线程同步
异常StackOverflowErrorOOM同样的异常

HotSpot 的特殊实现

在 HotSpot 中,Java 虚拟机栈和本地方法栈合二为一,使用同一个栈空间,由同一个 -Xss 参数控制:

bash
# HotSpot 中 -Xss 同时控制 Java 栈和本地方法栈
java -Xss2m MyApp

线程与两种栈

每个线程同时可能执行 Java 代码和 native 代码:

线程执行过程:

Java 方法 ──→ 本地方法 ──→ Java 方法
   │              │              │
   │ Java 栈     │ 本地栈      │ Java 栈
   │              │              │

当线程从 Java 方法调用 native 方法时:

  1. Java 栈帧入栈
  2. native 方法在本地方法栈上创建栈帧
  3. native 方法执行完毕,本地栈帧弹出
  4. 恢复 Java 栈的执行

本地方法与内存管理

native 方法的参数传递

当 Java 调用 native 方法时,参数通过 JNI 传递。JNI 环境指针 JNIEnv* 提供了访问 Java 对象的接口:

c
JNIEXPORT jstring JNICALL Java_NativeDemo_concat
    (JNIEnv *env, jobject this, jstring str1, jstring str2) {
    // JNIEnv 提供了操作 Java 对象的函数
    const char *s1 = (*env)->GetStringUTFChars(env, str1, 0);
    const char *s2 = (*env)->GetStringUTFChars(env, str2, 0);

    // ... 拼接逻辑 ...

    (*env)->ReleaseStringUTFChars(env, str1, s1);
    (*env)->ReleaseStringUTFChars(env, str2, s2);

    return result;
}

本地代码的内存管理

本地代码使用的内存不受 JVM 管理。如果 native 代码分配了本地内存(如 mallocnew),必须手动释放,否则就会造成本地内存泄漏

java
// 这段代码有内存泄漏风险
public class NativeLeak {
    static {
        System.loadLibrary("leaky");
    }

    public native void allocateNativeMemory(long size);

    // 如果 native 方法分配了内存但没有释放
    // 即使 JVM Full GC,也无法回收这部分内存
}

本地方法的使用场景

场景示例说明
系统级操作System.loadLibrary()加载动态库
性能关键代码HotSpot 内部实现JDK 核心库大量使用 native 方法
遗留系统集成CORBA、RMI访问老旧系统
硬件交互设备驱动直接操作硬件
高性能库Netty(native epoll)操作系统级优化
加密/压缩OpenSSL JNI 绑定使用高性能 C 库

Thread.interrupt() 与 native 方法

Thread.interrupt() 的实现大量依赖 native 代码:

java
// 当调用 thread.interrupt() 时:
// 1. native 代码设置线程的中断标志位
// 2. 如果线程阻塞在 native 方法中,native 代码会检查中断状态
// 3. 抛出 InterruptedException
public class InterruptNative {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            while (true) {
                // 检测中断标志
                if (Thread.interrupted()) {
                    break;
                }
            }
        });
        t.start();
        Thread.sleep(1000);
        t.interrupt();  // native 调用,设置中断状态
    }
}

本节小结

本地方法接口与本地方法栈的核心要点:

关键点说明
JNIJava Native Interface,Java 与本地代码的桥梁
native 方法声明native 关键字 + System.loadLibrary()
本地方法栈为 native 方法服务,与 Java 栈类似但用本地语言
HotSpot 合并实现Java 栈和本地栈共用 -Xss 参数
内存管理native 内存不受 JVM GC 管理,需手动释放

理解本地方法接口,有助于理解 JDK 核心库的底层实现,以及在集成遗留系统时的技术选型。

接下来进入 线程共享内存区域 部分,首先是 堆概述(唯一性/对象创建/GC)

基于 VitePress 构建