java、native内存共享--ByteBuffer使用总结

本文深入探讨DirectByteBuffer在Java与Native间高效数据交换的应用,详细解析DirectByteBuffer创建与使用方式,对比不同方法优劣,总结实践经验。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、概述

最近做一个项目,性能不达标,在软件实现中频繁的调用jni实现java、native的数据传递,后来把数据直接放在native,减少native的调用,性能优化明显。查了下java、native的内存共享,因此,了解到java的java.nio包。在项目中主要使用的是ByteBuffer,其他类感兴趣的自行查询。

ByteBuffer内存共享分为两种:1、java栈内存共享 2、native堆申请内存。
我这里使用的是第二种方式,也即DirectByteBuffer。第一种,我的理解是java层,不同线程间方便 内存共享。由于native和java的内存不在 一个空间,为了共享,必须为一个空间,在native申请内存,IO性能 会更好,也即DirectByteBuffer。

二、DirectByteBuffer的使用方式

1.native创建DirectByteBuffer,java通过native映射

优点:封装实现
缺点:ByteBuffer一些接口不可以使用,如array(),获取数据需要调用jni接口进行拷贝
代码实现:

status_t JNJBuffer::createByteBufferFromNJBuffer(
        JNIEnv *env, bool readOnly, bool clearBuffer, const sp<NJBuffer> &buffer,
        jobject *buf) const {
    // if this is an NJBuffer that doesn't actually hold any accessible memory,
    // use a null ByteBuffer
    *buf = NULL;

    if (buffer == NULL) {
        ALOGV("createByteBufferFromABuffer - given NULL, returning NULL");
        return OK;
    }

    if (buffer->base() == NULL) {
		ALOGV("createByteBufferFromABuffer ");
        return OK;
    }

    jobject byteBuffer =
        env->NewDirectByteBuffer(buffer->base(), buffer->capacity());
	ALOGD("--------readOnly = %d",readOnly);
    if (readOnly && byteBuffer != NULL) {
        jobject readOnlyBuffer = env->CallObjectMethod(
                byteBuffer, mByteBufferAsReadOnlyBufferMethodID);
        env->DeleteLocalRef(byteBuffer);
        byteBuffer = readOnlyBuffer;
    }
    if (byteBuffer == NULL) {
        return NO_MEMORY;
    }
    jobject me = env->CallObjectMethod(
            byteBuffer, mByteBufferOrderMethodID, mNativeByteOrderObj);
    env->DeleteLocalRef(me);
    me = env->CallObjectMethod(
            byteBuffer, mByteBufferLimitMethodID,
            clearBuffer ? buffer->capacity() : (buffer->offset() + buffer->size()));
    env->DeleteLocalRef(me);
    me = env->CallObjectMethod(
            byteBuffer, mByteBufferPositionMethodID,
            clearBuffer ? 0 : buffer->offset());
    env->DeleteLocalRef(me);
    me = NULL;

    *buf = byteBuffer;
    return OK;
}

status_t JNJBuffer::getBuffers(
        JNIEnv *env, bool input, sp<NJBuffer> buffers[], size_t bufferSize, jobjectArray *bufArray) const {
    if(buffers == NULL || bufferSize <= 0 || bufArray == NULL) {
		return BAD_VALUE;
    }
    *bufArray = (jobjectArray)env->NewObjectArray(
            bufferSize, mByteBufferClass, NULL);
    if (*bufArray == NULL) {
        return NO_MEMORY;
    }

    for (size_t i = 0; i < bufferSize; ++i) {
        const sp<NJBuffer> &buffer = buffers[i];

        jobject byteBuffer = NULL;
        status_t err = createByteBufferFromNJBuffer(
                env, false /* readOnly */, true /* clearBuffer */, buffer, &byteBuffer);
        if (err != OK) {
            return err;
        }
        if (byteBuffer != NULL) {
            env->SetObjectArrayElement(
                    *bufArray, i, byteBuffer);

            env->DeleteLocalRef(byteBuffer);
            byteBuffer = NULL;
        }
    }

    return OK;
}

java层调用getBuffers,获取java层映射的ByteBuffers数组,通过bufArray返回。
在java层,通过ByteBuffer的get、put等函数实现对buffer的操作。如果bu

2.java层创建buffer数组,native层获取对应的native buffer

优点:可以使用array,减少jni的调用。
缺点:buffer分配在java层,buffer大小可控性差,容易被开发修改
代码实现:

static jboolean com_icetech_randis_NetLinuxCamera_setBuffers(
		JNIEnv *env, jobject thiz, jint type, jobjectArray byteBuffers, jint size) {
	ALOGD("com_icetech_randis_NetLinuxCamera_setBuffers");
	sp<NetLinuxCamera> nlcdev = getNetLinuxCamera(env, thiz);
	if (nlcdev == NULL) {
		jniThrowException(env, "java/lang/NullPointerException", NULL);
		return false;
	}

	for(int i = 0;i < size;i++) {
		jobject byteBuffer = env->GetObjectArrayElement(byteBuffers, i);
		if(byteBuffer == NULL) {
			jniThrowException(env, "java/lang/NullPointerException", NULL);
			return false;
		}
		// 以下两行是重点,获取java buffer的native指针首地址及buffer的长度
		// 获取buffer首地址后就可以操作buffer,读写数据
    	void* buffer  =env->GetDirectBufferAddress(byteBuffer);
		int capacity = env->GetDirectBufferCapacity(byteBuffer);
		// 此处不用关心
		status_t err = nlcdev->initBuffer(type, i, buffer, capacity);
		if(err != OK) {
			return false;
		}
	}

	return true;
}

注意: 该方式在java层可以使用array(),但不能直接使用,需要配合arrayoffset()。在创建buffer时,为了内存地址对齐,有一定的偏移。
使用方式:

byte[] array = mCacheBuffers[currentIndex].array();
offset = mCacheBuffers[currentIndex].arrayOffset();
// 有效数据从offset开始
mCb.onVideoBufferAvailable(array, offset, length);
3.两种方式的共同点

若buffer作为循环buffer,native层要自己实现对buffer的管理;在java层,get、put后,position会自动移动,具体如何移动,没有用到没有深究。
若buffer不做为循环buffer,每次数据写完后需要执行buffer.clear(),该函数不会影响buffer中数据,只是把读写数据的位置归零,也不影响offset。

三、总结

之前开发写代码较少,一直想写代码,在写代码是思路较清晰,总是不能下手,比如使用java、native内存共享,使用arry()读取数据,java、native层数据迁移不调用jni接口,还有刚开始使用array()接口不能拿到正确数据,网上没有查到资料,需要写demo研究下,一拖再拖。真正去实现了,其实也挺简单的。

<think>好的,用户之前问过关于Caffeine缓存库的问题,现在又想知道Java怎么使用堆外内存。可能需要了解用户的学习阶段,是Java开发者吗?还是在做性能优化? 首先,用户可能在使用Java处理大数据或者需要高性能的场景,遇到了堆内存限制或GC问题,所以想用堆外内存。需要解释清楚堆外内存的概念、使用场景,以及具体的实现方法。 要确保覆盖基本方法,比如ByteBuffer,还有第三方库如Unsafe和Netty的ByteBuf。可能用户对JVM内存结构不太熟悉,需要简单说明堆内和堆外的区别,以及优缺点。 要注意用户可能的误区,比如堆外内存的管理问题,手动释放的重要性,避免内存泄漏。另外,可能需要提醒安全方面的考虑,比如Unsafe的使用风险。 用户可能想知道实际应用中的例子,比如什么时候应该用堆外内存,比如网络编程、缓存层或者避免GC停顿。需要给出代码示例,但也要注意不同方法的适用性。 另外,用户可能对性能优化感兴趣,所以需要比较堆外和堆内的性能差异,以及监控和管理堆外内存的工具,比如JMX或者第三方库。 还要考虑到Java版本的不同,比如新版Java是否有改进,或者推荐使用更安全的API,比如MemorySegment,尽管可能还在孵化阶段。 最后,总结最佳实践,帮助用户正确使用堆外内存,避免常见错误。</think>在 Java使用堆外内存(Off-Heap Memory)是突破 JVM 堆内存限制的重要技术手段,特别适合处理大规模数据、网络编程和需要减少 GC 停顿的场景。以下是具体实现方法和关键技术要点: --- ### 一、堆外内存核心原理 $$ \text{内存布局} = \underbrace{\text{JVM Heap}}_{\text{GC 管理}} \quad + \quad \underbrace{\text{Direct Memory}}_{\text{手动管理}} $$ - **优势**:绕过 GC 管理,避免 Full GC 停顿 - **劣势**:需手动内存管理,存在泄漏风险 - **典型应用**:Netty 的零拷贝、缓存系统、科学计算 --- ### 二、标准 API 实现 #### 1. ByteBuffer(NIO) ```java // 分配 1MB 堆外内存(单位:字节) ByteBuffer buffer = ByteBuffer.allocateDirect(1024 * 1024); try { // 写入数据 buffer.putInt(2023); buffer.putDouble(3.1415926535); // 读取数据前必须 flip buffer.flip(); int year = buffer.getInt(); double pi = buffer.getDouble(); } finally { // 显式释放(实际由 Cleaner 管理) ((DirectBuffer) buffer).cleaner().clean(); } ``` **特点**: - 自动内存对齐(默认按平台对齐方式) - 最大容量受 `-XX:MaxDirectMemorySize` 限制(默认与堆内存一致) --- #### 2. Unsafe(底层操作) ```java Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe"); theUnsafe.setAccessible(true); Unsafe unsafe = (Unsafe) theUnsafe.get(null); // 分配 100 字节堆外内存 long address = unsafe.allocateMemory(100); try { // 写入字符串(需处理字符编码) byte[] strBytes = "Hello Off-Heap".getBytes(StandardCharsets.UTF_8); unsafe.copyMemory(strBytes, 16, null, address, strBytes.length); // 读取操作 byte[] readBuffer = new byte[strBytes.length]; unsafe.copyMemory(null, address, readBuffer, 16, strBytes.length); System.out.println(new String(readBuffer, StandardCharsets.UTF_8)); } finally { unsafe.freeMemory(address); // 必须显式释放 } ``` **注意事项**: - JDK 9+ 需要添加 JVM 参数:`--add-opens java.base/jdk.internal.misc=ALL-UNNAMED` - 需自行处理内存对齐问题 --- ### 三、第三方库方案 #### 1. Netty 的 ByteBuf ```java // 创建非池化堆外内存 ByteBuf buffer = Unpooled.directBuffer(1024); try { buffer.writeInt(2023); buffer.writeDouble(3.1415926535); // 读取需要手动维护读写索引 int year = buffer.readInt(); double pi = buffer.readDouble(); } finally { buffer.release(); // 引用计数释放 } ``` **优点**:支持内存池、引用计数等高级特性 --- #### 2. Javolution 的 DirectMemory ```java DirectMemory mem = new DirectMemory(1024); mem.setByte(0, (byte) 0xAA); // 直接地址操作 mem.getByte(0); mem.deallocate(); // 释放内存 ``` --- ### 四、内存管理关键技术 #### 1. 内存跟踪工具 ```bash # 查看堆外内存使用 jcmd <pid> VM.native_memory summary # 开启详细追踪(需开启 NMT) -XX:NativeMemoryTracking=detail ``` #### 2. 性能优化技巧 - **内存池化**:使用 Netty 的 `PooledByteBufAllocator` - **缓存行对齐**:避免伪共享(Padding 到 64 字节) - **批量操作**:使用 `Unsafe.copyMemory` 代替单元素操作 --- ### 五、新型 API(JDK 14+) #### Foreign-Memory Access API(孵化阶段) ```java try (MemorySegment segment = MemorySegment.allocateNative(100)) { VarHandle intHandle = MemoryLayout.ofSequence(MemoryLayouts.JAVA_INT) .varHandle(int.class, MemoryLayout.PathElement.sequenceElement()); intHandle.set(segment.baseAddress(), 0, 123); // 类型安全访问 int value = (int) intHandle.get(segment.baseAddress(), 0); } ``` **优势**:类型安全的内存访问,避免直接操作指针 --- ### 六、典型应用场景对比 ```plaintext 场景 | 推荐方案 -------------------|------------------- 网络IO缓冲区 | Netty ByteBuf 大文件内存映射 | MappedByteBuffer 跨进程共享内存 | Unsafe + 共享内存 科学计算 | Javolution ``` --- ### 注意事项 1. **内存泄漏预防**:必须成对使用 allocate/free 2. **异常处理**:添加 `OutOfMemoryError` 处理逻辑 3. **性能监控**:使用 `BufferPoolMXBean` 监控 DirectBuffer 使用 4. **安全限制**:Unsafe 操作需开启模块访问权限 > **最佳实践**:建议通过 `Runtime.getRuntime().maxMemory()` 设置合理的 `-XX:MaxDirectMemorySize`(通常设置为堆内存的 50-80%)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值