Java高级程序设计

本地代码交互

JNI / JNA / Unsafe

为什么需要本地代码交互?

Java 虽然功能强大,但在某些场景下需要与本地代码(C/C++)交互:

  • 性能优化:调用高性能的 C/C++ 库
  • 系统调用:访问操作系统底层功能
  • 硬件交互:直接操作硬件设备
  • 遗留代码:集成现有的 C/C++ 代码库
  • 特殊功能:Java 无法直接实现的功能

JNI (Java Native Interface)

JNI 是 Java 平台提供的标准编程接口,用于实现 Java 代码与本地代码(C/C++)之间的互操作。

特点

  • 标准接口:Java 平台原生支持
  • 双向调用:Java 可以调用本地代码,本地代码也可以调用 Java
  • 类型映射:提供 Java 类型与本地类型的映射
  • 内存管理:需要手动管理本地内存

https://docs.oracle.com/javase/8/docs/technotes/guides/jni/

JNI 工作流程

Java 代码
   ↓
javac 编译
   ↓
javah 生成头文件
   ↓
C/C++ 实现本地方法
   ↓
编译为动态库 (.so/.dll/.dylib)
   ↓
System.loadLibrary() 加载
   ↓
调用本地方法

JNI 示例:Hello World

Java 代码

public class HelloJNI {
    // 声明本地方法
    public native void sayHello();
    
    // 加载动态库
    static {
        System.loadLibrary("hello");  // 加载 libhello.so (Linux) 或 hello.dll (Windows)
    }
    
    public static void main(String[] args) {
        new HelloJNI().sayHello();
    }
}

JNI 示例:生成头文件

使用 javah 生成 C 头文件:

javac HelloJNI.java
javah HelloJNI

生成的头文件 HelloJNI.h

#include <jni.h>

JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *, jobject);

JNI 示例:C 实现

#include <jni.h>
#include <stdio.h>
#include "HelloJNI.h"

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

编译为动态库:

# Linux
gcc -shared -fPIC -I${JAVA_HOME}/include -I${JAVA_HOME}/include/linux \
    HelloJNI.c -o libhello.so

# macOS
gcc -shared -fPIC -I${JAVA_HOME}/include -I${JAVA_HOME}/include/darwin \
    HelloJNI.c -o libhello.dylib

# Windows
gcc -shared -I"%JAVA_HOME%\include" -I"%JAVA_HOME%\include\win32" \
    HelloJNI.c -o hello.dll

JNI 类型映射

Java 类型 JNI 类型 C 类型
boolean jboolean unsigned char
byte jbyte signed char
char jchar unsigned short
short jshort short
int jint int
long jlong long long
float jfloat float
double jdouble double
Object jobject void*
String jstring -
int[] jintArray -

JNI 字符串操作

// Java String 转 C 字符串
JNIEXPORT void JNICALL Java_Example_printString(JNIEnv *env, jobject obj, jstring str) {
    const char *cstr = (*env)->GetStringUTFChars(env, str, NULL);
    if (cstr == NULL) {
        return;  // 内存不足
    }
    printf("%s\n", cstr);
    // 必须释放字符串
    (*env)->ReleaseStringUTFChars(env, str, cstr);
}

重要:使用 GetStringUTFChars 获取的字符串必须用 ReleaseStringUTFChars 释放!

JNI 数组操作

JNIEXPORT jintArray JNICALL Java_Example_doubleArray(JNIEnv *env, jobject obj, jintArray arr) {
    // 获取数组长度
    jsize len = (*env)->GetArrayLength(env, arr);
    // 获取数组元素(可能复制,也可能直接访问)
    jint *body = (*env)->GetIntArrayElements(env, arr, NULL);
    // 操作数组
    for (int i = 0; i < len; i++) {
        body[i] *= 2;
    }
    // 释放数组(isCopy 表示是否复制)
    (*env)->ReleaseIntArrayElements(env, arr, body, 0);
    return arr;
}

JNI 调用 Java 方法

JNIEXPORT void JNICALL Java_Example_callJavaMethod(JNIEnv *env, jobject obj) {
    // 1. 获取类对象
    jclass cls = (*env)->GetObjectClass(env, obj);
    // 2. 获取方法 ID
    jmethodID mid = (*env)->GetMethodID(env, cls, "printMessage", "(Ljava/lang/String;)V");
    // 3. 创建字符串参数
    jstring msg = (*env)->NewStringUTF(env, "Hello from JNI!");
    // 4. 调用方法
    (*env)->CallVoidMethod(env, obj, mid, msg);
    // 5. 释放本地引用
    (*env)->DeleteLocalRef(env, msg);
}

JNI 异常处理

JNIEXPORT void JNICALL Java_Example_handleException(JNIEnv *env, jobject obj) {
    // 调用可能抛出异常的方法
    jclass cls = (*env)->FindClass(env, "java/lang/Exception");
    jmethodID constructor = (*env)->GetMethodID(env, cls, "<init>", "(Ljava/lang/String;)V");
    jstring msg = (*env)->NewStringUTF(env, "Error occurred");
    jobject exception = (*env)->NewObject(env, cls, constructor, msg);
    // 抛出异常
    (*env)->Throw(env, exception);
    // 检查是否有异常
    if ((*env)->ExceptionCheck(env)) {
        (*env)->ExceptionDescribe(env);
        (*env)->ExceptionClear(env);
    }
}

优点

  • 标准接口:Java 平台原生支持
  • 性能高:直接调用本地代码,无额外开销
  • 功能强大:可以访问所有系统功能
  • 双向调用:本地代码可以回调 Java

缺点

  • 平台相关:需要为不同平台编译
  • 内存管理:容易内存泄漏
  • 类型转换:需要手动处理类型映射
  • 调试困难:跨语言调试复杂

JNA (Java Native Access)

JNA 是一个 Java 库,提供了更简单的方式来访问本地代码,无需编写 C/C++ 代码

  • 纯 Java:只需要 Java 代码
  • 动态绑定:运行时动态加载动态库
  • 类型映射:自动处理类型转换
  • 易于使用:比 JNI 简单得多

https://github.com/java-native-access/jna

JNA 工作原理

Java 代码
   ↓
JNA 库
   ↓
动态加载 .so/.dll/.dylib
   ↓
自动类型映射
   ↓
调用 C 函数

关键:只需要已有的 C 动态库,不需要编写 C 代码!

JNA 示例:调用 C 标准库

import com.sun.jna.Library;
import com.sun.jna.Native;

// 定义接口映射到 C 库
public interface CLibrary extends Library {
    CLibrary INSTANCE = (CLibrary) Native.load("c", CLibrary.class);
    
    // 映射到 printf 函数
    void printf(String format, Object... args);
}

public class JNAExample {
    public static void main(String[] args) {
        CLibrary.INSTANCE.printf("Hello from C: %s\n", "JNA");
    }
}

JNA 工作原理:为什么 printf 能映射到 C 的 printf

1. 动态库加载

Native.load("c", CLibrary.class)
  • "c" 表示加载 C 标准库(Linux: libc.so,macOS: libc.dylib,Windows: msvcrt.dll
  • JNA 会根据平台自动查找对应的动态库

2. 方法名匹配

JNA 使用动态代理机制:

  • 当调用 CLibrary.INSTANCE.printf(...)
  • JNA 拦截方法调用,获取方法名 "printf"
  • 在已加载的动态库中查找同名函数 printf
  • 如果找不到,会尝试添加下划线前缀(某些平台的 C 函数有 _printf

3. 类型自动转换

void printf(String format, Object... args);
  • Stringchar*(C 字符串)
  • Object... → 根据实际类型转换(如 intintStringchar*
  • JNA 自动处理内存分配和释放

4. 调用流程

Java: CLibrary.INSTANCE.printf("Hello", "World")
  ↓
JNA 动态代理拦截
  ↓
查找 C 函数: printf
  ↓
类型转换: String → char*
  ↓
调用 C: printf("Hello", "World")
  ↓
返回结果(如果有)

关键点:方法名必须与 C 函数名完全匹配(或使用 @FunctionName 注解指定)

JNA 类型映射

C 类型 JNA Java 类型
char* String
int int
long long
double double
void* Pointer
struct Structure
function pointer Callback

JNA 示例:调用系统函数

import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Platform;

public interface Kernel32 extends Library {
    Kernel32 INSTANCE = (Kernel32) Native.load(
        Platform.isWindows() ? "kernel32" : null, 
        Kernel32.class
    );
    
    // Windows API: GetSystemTime
    void GetSystemTime(SYSTEMTIME result);
}
// 定义结构体
public class SYSTEMTIME extends Structure {
    public short wYear;
    public short wMonth;
    public short wDayOfWeek;
    public short wDay;
    public short wHour;
    public short wMinute;
    public short wSecond;
    public short wMilliseconds;
    
    // 显式指定字段顺序(推荐,确保顺序正确)
    @Override
    protected List<String> getFieldOrder() {
        return Arrays.asList(
            "wYear", "wMonth", "wDayOfWeek", "wDay",
            "wHour", "wMinute", "wSecond", "wMilliseconds"
        );
    }
}

结构体映射要求:必须与 C 结构体完全匹配

C 中的 SYSTEMTIME 结构体定义(Windows API)

typedef struct _SYSTEMTIME {
    WORD wYear;         // short (16位)
    WORD wMonth;        // short
    WORD wDayOfWeek;    // short
    WORD wDay;          // short
    WORD wHour;         // short
    WORD wMinute;       // short
    WORD wSecond;       // short
    WORD wMilliseconds; // short
} SYSTEMTIME;

匹配要求

  1. 字段类型必须匹配

    • C: WORD (16位无符号整数) → Java: short
    • C: DWORD (32位) → Java: int
    • C: char* → Java: StringPointer
  2. 字段顺序必须匹配

    • 必须按照 C 结构体中的声明顺序定义
  3. 字段名可以不同(但建议保持一致)

    public class SYSTEMTIME extends Structure {
        public short year;      // 对应 wYear
        public short month;      // 对应 wMonth
        // ...
    }
    

为什么必须完全匹配?

JNA 通过内存布局来映射结构体:

C 结构体内存布局:
[0-1]   wYear (short)
[2-3]   wMonth (short)
[4-5]   wDayOfWeek (short)
...

如果 Java 结构体定义不匹配,会导致:

  • 读取错误的数据
  • 内存越界,程序崩溃

JNA 结构体示例

public class Point extends Structure {
    public int x;
    public int y;
    @Override
    protected List<String> getFieldOrder() {
        return Arrays.asList("x", "y");
    }
}

Point p = new Point();
p.x = 10;
p.y = 20;
p.write();  // 写入本地内存

JNA 回调机制:C → Java

通过 Callback 接口可以让 C 函数回调 Java。

假设有一个 C 函数,接受一个回调函数:

// C 代码
typedef void (*CallbackFunc)(int value);

void processData(int count, CallbackFunc callback) {
    for (int i = 0; i < count; i++) {
        callback(i * 10);  // 调用回调函数
    }
}

Java 实现回调

import com.sun.jna.Callback;
import com.sun.jna.Library;
import com.sun.jna.Native;
// 定义回调接口
public interface DataCallback extends Callback {
    void callback(int value);
}
// 定义 C 库接口
public interface MyLibrary extends Library {
    MyLibrary INSTANCE = Native.load("mylib", MyLibrary.class);
    // 函数指针映射为 Callback
    void processData(int count, DataCallback callback);
}

public class CallbackExample {
    public static void main(String[] args) {
        DataCallback callback = new DataCallback() {
            @Override
            public void callback(int value) {
                System.out.println("Received: " + value);
            }
        };
        MyLibrary.INSTANCE.processData(5, callback);

    }
}
    // Received: 0
    // Received: 10
    // Received: 20
    // Received: 30
    // Received: 40

回调机制说明

工作原理

  1. 定义 Callback 接口:继承 com.sun.jna.Callback
  2. 方法签名匹配:Java 回调方法签名必须与 C 函数指针签名匹配
  3. 自动转换:JNA 自动将 Java 对象转换为函数指针
  4. 生命周期管理:回调对象必须保持引用,否则可能被 GC 回收

JNA vs JNI

特性 JNI JNA
代码复杂度 需要 C/C++ 代码 纯 Java
性能 更快(直接调用) 稍慢(通过反射)
类型映射 手动处理 自动处理
平台支持 需要为每个平台编译 跨平台
学习曲线 陡峭 平缓
双向调用 支持(本地代码回调 Java) 支持(通过 Callback)
适用场景 高性能、复杂交互 简单调用、快速开发

JNA 的优缺点

优点

  • 简单易用:纯 Java,无需 C/C++ 代码
  • 快速开发:几分钟就能调用 C 函数
  • 跨平台:自动处理平台差异
  • 类型安全:编译时检查

缺点

  • 性能开销:比 JNI 慢(反射调用)
  • 内存管理:复杂结构体需要手动管理
  • 调试困难:错误信息不够清晰
  • 依赖库:需要引入 JNA 依赖

Java Unsafe

sun.misc.Unsafe 是 Java 提供的一个内部 API,提供了直接操作内存的能力。

特点

  • 底层操作:直接操作内存、CAS、对象实例化
  • 不受限制:绕过 Java 的安全检查
  • 高风险:可能导致 JVM 崩溃
  • 内部 API:不保证向后兼容

获取 Unsafe 实例

import sun.misc.Unsafe;
import java.lang.reflect.Field;
public class UnsafeExample {
    private static Unsafe getUnsafe() {
        try {
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            return (Unsafe) field.get(null);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    public static void main(String[] args) {
        Unsafe unsafe = getUnsafe();
        // 使用 unsafe...
    }
}

Unsafe:直接内存操作

Unsafe unsafe = getUnsafe();

// 分配内存(字节)
long address = unsafe.allocateMemory(1024);

// 设置内存值
unsafe.putInt(address, 42);
unsafe.putLong(address + 4, 100L);

// 读取内存值
int value = unsafe.getInt(address);
long longValue = unsafe.getLong(address + 4);

// 释放内存
unsafe.freeMemory(address);

注意:直接内存操作非常危险,可能导致内存泄漏或 JVM 崩溃!

Unsafe:CAS 操作

public class CASExample {
    private static final Unsafe unsafe = getUnsafe();
    private static final long valueOffset;
    private volatile int value;
    
    static {
        try {
            valueOffset = unsafe.objectFieldOffset(
                CASExample.class.getDeclaredField("value"));
        } catch (Exception e) {
            throw new Error(e);
        }
    }
    
    public boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }
}

AtomicInteger 等类内部使用 Unsafe 实现。

Unsafe:对象操作

Unsafe unsafe = getUnsafe();

// 不调用构造函数创建对象
MyClass obj = (MyClass) unsafe.allocateInstance(MyClass.class);

// 获取对象字段偏移量
long offset = unsafe.objectFieldOffset(MyClass.class.getDeclaredField("field"));

// 直接设置字段值(绕过访问控制)
unsafe.putObject(obj, offset, "value");

// 直接读取字段值
Object value = unsafe.getObject(obj, offset);

Unsafe:数组操作

Unsafe unsafe = getUnsafe();

int[] array = new int[10];

// 获取数组第一个元素的偏移量
int baseOffset = unsafe.arrayBaseOffset(int[].class);
// 获取数组中每个元素的大小
int indexScale = unsafe.arrayIndexScale(int[].class);

// 直接设置数组元素(绕过边界检查)
unsafe.putInt(array, baseOffset + 5 * indexScale, 42);

// 直接读取数组元素
int value = unsafe.getInt(array, baseOffset + 5 * indexScale);

Unsafe 的应用场景

1. 高性能库

  • Netty:直接内存管理(DirectByteBuffer)
  • Disruptor:无锁队列
  • Kryo:序列化框架

2. 并发工具

  • AtomicInteger/Long:CAS 操作
  • LockSupport:线程阻塞/唤醒

3. 反射增强

  • 绕过访问控制
  • 不调用构造函数创建对象

Unsafe 的风险

  1. 内存泄漏:手动分配的内存必须手动释放
  2. JVM 崩溃:错误的地址操作可能导致 JVM 崩溃
  3. 安全绕过:绕过 Java 的安全检查
  4. 平台相关:不同平台行为可能不同
  5. 版本兼容:内部 API,不保证向后兼容

实际应用:Netty 的 DirectByteBuffer

// Netty 使用 Unsafe 优化直接内存分配
public class DirectByteBuffer {
    private static final Unsafe unsafe = getUnsafe();
    public static ByteBuffer allocateDirect(int capacity) {
        // 使用 Unsafe 分配直接内存
        long address = unsafe.allocateMemory(capacity);
        return new DirectByteBuffer(address, capacity);
    }
    // 直接内存访问,避免 JVM 堆内存拷贝
    public byte get(int index) {
        return unsafe.getByte(address + index);
    }
}

减少 JVM 堆内存与操作系统内存之间的拷贝,提高 I/O 性能。

实际应用:Disruptor 无锁队列

public class RingBuffer {
    private static final Unsafe unsafe = getUnsafe();
    private final long[] buffer;
    private volatile long cursor = 0;
    
    public boolean tryPublish(long sequence) {
        long current = cursor;
        // CAS 更新,无需加锁
        return unsafe.compareAndSwapLong(this, cursorOffset, current, sequence);
    }
}

无锁设计,高并发场景下性能优于传统锁机制。

实际应用:MapDB 的堆外内存

MapDB 是一个高性能的嵌入式 Java 数据库引擎,提供基于磁盘或堆外存储的并发集合。https://mapdb.org/

MapDB 简介

  • 嵌入式数据库:无需单独的数据库服务器
  • 多种存储模式:堆内存、堆外内存、文件存储
  • 高性能:通过堆外内存避免 GC 压力
  • 事务支持:ACID 事务、MVCC 隔离

MapDB 使用示例

import org.mapdb.*;

// 创建基于堆外内存的数据库
DB db = DBMaker
    .memoryDirectDB()  // 使用 DirectByteBuffer(间接使用 Unsafe)
    .transactionEnable()
    .make();

// 创建 Map
HTreeMap<String, String> map = db
    .hashMap("myMap")
    .createOrOpen();

MapDB 使用示例

// 操作数据
map.put("key1", "value1");
map.put("key2", "value2");

// 提交事务
db.commit();

// 关闭数据库
db.close();

MapDB 的存储架构

当调用 map.put("key", "value") 时,数据是如何存储的?

HTreeMap.put("key", "value")
  ↓
MapDB 内部序列化(key + value)
  ↓
MemoryStore(内存存储层)
  ↓
DirectByteBuffer(堆外内存)
  ↓
Unsafe(底层内存操作)

详细流程

  1. HTreeMap.put():用户调用 map.put("key", "value")
  2. 序列化:MapDB 将 key 和 value 序列化为字节数组
  3. MemoryStore:MapDB 的 MemoryStore 负责管理存储
    • 使用 DirectByteBuffer 作为底层存储
    • 维护索引结构(哈希表、B+树等)
  4. DirectByteBuffer:数据写入堆外内存
    • 通过 DirectByteBuffer.put() 写入字节
    • 内部使用 Unsafe 直接操作内存
  5. 持久化(可选):如果使用文件存储,数据会同步到磁盘

代码示例(简化版)

// MapDB 内部实现(简化版)
public class HTreeMap<K, V> {
    private MemoryStore store;  // 存储层
    
    public V put(K key, V value) {
        // 1. 序列化
        byte[] keyBytes = serializer.serialize(key);
        byte[] valueBytes = serializer.serialize(value);
        
        // 2. 写入 MemoryStore(底层使用 DirectByteBuffer)
        long address = store.allocate(keyBytes.length + valueBytes.length);
        DirectByteBuffer buffer = store.getBuffer(address);
        buffer.put(keyBytes);
        buffer.put(valueBytes);
        
        // 3. 更新索引
        index.put(key, address);
        
        return value;
    }
}

代码示例(简化版)

// MemoryStore 内部使用 DirectByteBuffer
public class MemoryStore {
    private DirectByteBuffer buffer;  // 堆外内存缓冲区
    public long allocate(int size) {
        // 在 DirectByteBuffer 中分配空间
        long address = currentOffset;
        currentOffset += size;
        return address;
    }
    public DirectByteBuffer getBuffer(long address) {
        // 返回 DirectByteBuffer(内部使用 Unsafe)
        return buffer;
    }
}

关键点

  • HTreeMap 是 MapDB 提供的集合接口(类似 HashMap)
  • MemoryStore 是 MapDB 的存储抽象层
  • DirectByteBuffer 是实际的存储介质(堆外内存)
  • Unsafe 是 DirectByteBuffer 的底层实现

数据流向HTreeMap.put() → 序列化 → MemoryStoreDirectByteBufferUnsafe

MapDB 与 Unsafe:间接使用

MapDB 不直接使用 Unsafe,但通过 DirectByteBuffer 间接使用 Unsafe 来实现堆外内存存储。

DB db = DBMaker
    .memoryDirectDB()  // 使用堆外内存
    .make();
HTreeMap<String, String> map = db.hashMap("map").createOrOpen();
map.put("key", "value");
  • 避免 GC:数据存储在堆外,不受 GC 影响
  • 大内存支持:可以存储超过堆内存限制的数据
  • 性能提升:减少内存拷贝,提高 I/O 性能

DirectByteBuffer 与 Unsafe

MapDB 的堆外内存功能依赖于 DirectByteBuffer,而 DirectByteBuffer 内部使用 Unsafe:

public class DirectByteBuffer extends MappedByteBuffer {
    private static final Unsafe unsafe = getUnsafe();
    private final long address;  // 堆外内存地址
    DirectByteBuffer(int cap) {
        address = unsafe.allocateMemory(cap);
        // ...
    }
    public byte get(int i) {
        return unsafe.getByte(address + i);
    }
    public void put(int i, byte b) {
        unsafe.putByte(address + i, b);
    }
}

MapDB 与内存映射(Memory Mapping)

MapDB 支持内存映射技术,通过 MappedByteBuffer 将文件映射到内存,实现高性能的磁盘 I/O。

工作原理

  • 使用 mmap 系统调用将文件映射到虚拟内存
  • 读写操作直接操作内存,由操作系统负责同步到磁盘
  • 避免用户态和内核态之间的数据拷贝

内存映射的工作原理

// MappedByteBuffer 内部使用 mmap 系统调用
FileChannel channel = new RandomAccessFile("data.db", "rw").getChannel();
MappedByteBuffer buffer = channel.map(
    FileChannel.MapMode.READ_WRITE,  // 读写模式
    0,                                 // 起始位置
    1024 * 1024                        // 映射大小(1MB)
);

// 直接操作内存,操作系统自动同步到磁盘
buffer.put(0, (byte) 42);
byte value = buffer.get(0);

MapDB 启用内存映射 (性能可提升数十倍)

import org.mapdb.*;

// 基于文件的数据库,启用内存映射
DB db = DBMaker
    .fileDB("data.db")
    .fileMmapEnableIfSupported()  // 启用内存映射(如果支持)
    .fileMmapPreclearDisable()     // 禁用预清除,提高写入性能
    .transactionEnable()
    .make();
HTreeMap<String, String> map = db.hashMap("myMap").createOrOpen();

map.put("key", "value");
db.commit();

MappedByteBuffer 与 Unsafe

MappedByteBuffer 继承自 DirectByteBuffer,也使用 Unsafe 进行内存操作:

// MappedByteBuffer 继承关系
MappedByteBuffer extends DirectByteBuffer extends ByteBuffer

// 内部使用 Unsafe 访问映射的内存
public class MappedByteBuffer extends DirectByteBuffer {
    // 通过 mmap 系统调用映射文件
    // 使用 Unsafe 直接访问映射的内存区域
    public byte get(int index) {
        return unsafe.getByte(address + index);
    }
}

MapDB 的存储模式对比

存储模式 技术 特点 适用场景
堆内存 普通 Java 对象 受 GC 影响,速度快 小数据,临时数据
堆外内存 DirectByteBuffer 不受 GC 影响,速度快 大数据,需要避免 GC
文件 + 内存映射 MappedByteBuffer 持久化,高性能 I/O 需要持久化的大数据
文件(传统) FileChannel 持久化,性能一般 小数据,简单持久化

MapDB vs 传统集合

特性 HashMap (堆内存) MapDB (堆外内存)
GC 影响 受 GC 影响,可能停顿 不受 GC 影响
内存限制 受堆内存限制 可超过堆内存限制
持久化 不支持 支持(可选)
性能 快(小数据) 快(大数据,无 GC)
底层技术 普通 Java 对象 DirectByteBuffer → Unsafe

MapDB关键技术

  • MapDB 支持两种高性能模式:堆外内存和内存映射
  • 两种模式都间接使用 Unsafe(通过 DirectByteBuffer/MappedByteBuffer)
  • 内存映射模式额外使用 mmap 系统调用实现文件映射
  • MapDB 不使用 JNI/JNA(纯 Java + 标准库)