NIO中的heap Buffer和direct Buffer区别

本文详细解析了Java NIO中HeapBuffer与DirectBuffer的不同之处,包括它们的创建方式、内存分配位置及垃圾回收机制。此外,还讨论了两者在性能上的优缺点,并给出了实际应用中的建议。

在Java的NIO中,我们一般采用ByteBuffer缓冲区来传输数据,一般情况下我们创建Buffer对象是通过ByteBuffer的两个静态方法:

ByteBuffer.allocate(int capacity);
ByteBuffer.wrap(byte[] array);

查看相关的源码得到

public static ByteBuffer allocate(int capacity) {
    if (capacity < 0)
        throw new IllegalArgumentException();
    return new HeapByteBuffer(capacity, capacity);
}
public static ByteBuffer wrap(byte[] array,
                                int offset, int length){
    try {
        return new HeapByteBuffer(array, offset, length);
    } catch (IllegalArgumentException x) {
        throw new IndexOutOfBoundsException();
    }
}

我们可以很清楚的发现,这两个方法都是实例化HeapByteBuffer来创建的ByteBuffer对象,也就是heap buffer. 其实除了heap buffer以外还有一种buffer,叫做direct buffer。我们也可以创建这一种buffer,通过ByteBuffer.allocateDirect(int capacity)方法,查看JDK源码如下:

public static ByteBuffer allocateDirect(int capacity) {
    return new DirectByteBuffer(capacity);
}
DirectByteBuffer(int cap) {                   // package-private

    super(-1, 0, cap, cap);
    boolean pa = VM.isDirectMemoryPageAligned();
    int ps = Bits.pageSize();
    long size = Math.max(1L, (long)cap + (pa ? ps : 0));
    Bits.reserveMemory(size, cap);

    long base = 0;
    try {
        base = unsafe.allocateMemory(size);
    } catch (OutOfMemoryError x) {
        Bits.unreserveMemory(size, cap);
        throw x;
    }
    unsafe.setMemory(base, size, (byte) 0);
    if (pa && (base % ps != 0)) {
        // Round up to page boundary
        address = base + ps - (base & (ps - 1));
    } else {
        address = base;
    }
    cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
    att = null;
}

那么heap buffer和direct buffer有什么区别呢?

heap buffer这种缓冲区是分配在堆上面的,直接由Java虚拟机负责垃圾回收,可以直接想象成一个字节数组的包装类。

direct buffer则是通过JNI在Java的虚拟机外的内存中分配了一块缓冲区(所以即使在运行时通过-Xmx指定了Java虚拟机的最大堆内存,还是可以实例化超出该大小的Direct ByteBuffer),该块并不直接由Java虚拟机负责垃圾回收收集,但是在direct buffer包装类被回收时,会通过Java Reference机制来释放该内存块。(但Direct Buffer的JAVA对象是归GC管理的,只要GC回收了它的JAVA对象,操作系统才会释放Direct Buffer所申请的空间)

两者各有优劣势:direct buffer对比 heap buffer:

劣势:创建和释放Direct Buffer的代价比Heap Buffer得要高;

优势:当我们把一个Direct Buffer写入Channel的时候,就好比是“内核缓冲区”的内容直接写入了Channel,这样显然快了,减少了数据拷贝(因为我们平时的read/write都是需要在I/O设备与应用程序空间之间的“内核缓冲区”中转一下的)。而当我们把一个Heap Buffer写入Channel的时候,实际上底层实现会先构建一个临时的Direct Buffer,然后把Heap Buffer的内容复制到这个临时的Direct Buffer上,再把这个Direct Buffer写出去。当然,如果我们多次调用write方法,把一个Heap Buffer写入Channel,底层实现可以重复使用临时的Direct Buffer,这样不至于因为频繁地创建和销毁Direct Buffer影响性能。

结论:Direct Buffer创建和销毁的代价很高,所以要用在尽可能重用的地方。 比如周期长传输文件大采用direct buffer,不然一般情况下就直接用heap buffer 就好。

Refer:

http://blog.youkuaiyun.com/u010853261/article/details/53464322

https://www.jianshu.com/p/007052ee3773

http://blog.youkuaiyun.com/xieyuooo/article/details/7547435

<think>好的,我现在需要解决用户提到的在Android平台上,Netty的i.n.util.internal.PlatformDependent提示低级API访问直接缓冲区不可靠,导致系统不稳定的问题。用户希望找到解决方案,避免使用堆缓冲区被优先选择的情况。 首先,我得理解这个警告的原因。根据引用[2]中的日志信息,PlatformDependent检测到当前平台(这里是Android)无法可靠地使用低级API来访问直接缓冲区,因此Netty会自动优先使用堆缓冲区,以防止系统不稳定。直接缓冲区(Direct Buffer)通常通过Java NIO的ByteBuffer.allocateDirect方法分配,它们存在于JVM堆外的内存中,可能在某些情况下提高I/O操作的性能,但管理使用不当可能导致内存泄漏或稳定性问题。 接下来,我需要考虑Android平台的特殊性。Android虽然基于Java,但其底层实现与标准JVM有所不同,尤其是在内存管理本地API的支持上。可能的情况是,Netty在检测Android环境时,发现某些用于操作直接缓冲区的底层方法不可用或不稳定,因此触发了这个警告,并切换到堆缓冲区。 用户的需求是解决这个问题,让直接缓冲区能够可靠使用,或者找到替代方案以避免系统不稳定。可能的解决方案包括: 1. **检查Netty版本**:首先确认使用的Netty版本是否较旧,可能存在对Android支持不佳的问题。升级到较新的版本可能已经修复了相关兼容性问题。 2. **显式请求直接缓冲区**:根据引用[1]中的代码,Netty在分配直接缓冲区时使用ByteBuffer.allocateDirect。用户可能需要强制Netty使用直接缓冲区,但需要注意这可能导致稳定性问题。可以通过设置系统属性-Dio.netty.noPreferDirect=false来尝试,但需要验证在Android上的效果。 3. **配置Netty的缓冲区分配器**:Netty允许自定义缓冲区分配器,用户可能需要实现一个适用于Android的分配器,或者调整现有配置,确保在Android上能够安全使用直接缓冲区。 4. **使用堆外内存管理库**:如果Netty的直接缓冲区在Android上确实存在问题,可能需要寻找其他管理堆外内存的方法,或者使用Android提供的NDK接口来操作本地内存,但这会增加复杂性。 5. **性能优化替代方案**:如果直接缓冲区不可行,考虑通过优化堆缓冲区的使用来减少性能损失,例如使用池化技术(如Netty的PooledByteBufAllocator)来重用缓冲区,降低内存分配开销。 此外,需要参考引用[3]提到的Chromium相关方法,但可能关联不大,不过可以看看是否有类似处理低级API不可靠的策略,比如降级使用或兼容层。 接下来,需要验证这些方案的可行性。例如,检查Netty的官方文档或社区是否有针对Android平台的建议,是否存在已知的issue或解决方案。可能存在的挑战包括Android版本差异、硬件差异导致的API支持不一致,以及Netty在Android上的测试覆盖情况。 最后,综合这些信息,向用户提供分步骤的解决方案,包括版本检查、配置调整、替代方法等,同时提醒潜在的风险注意事项,如系统稳定性测试内存监控。</think>针对Android平台上Netty提示直接缓冲区访问不可靠的问题,解决方案可分为以下步骤: ### 一、问题根源分析 1. **直接缓冲区原理**:直接缓冲区(DirectByteBuffer)通过$ByteBuffer.allocateDirect()$分配堆外内存,减少JVM堆与Native内存间的复制开销[^1] 2. **Android限制**: - ART运行时对JNI临界区访问有严格限制 - 部分Unix系统调用在Android Bionic libc中未实现 - 硬件碎片化导致内存对齐要求差异 ### 二、解决方案实施 #### 1. 强制使用直接缓冲区(需稳定性测试) ```java // 启动时设置系统属性 System.setProperty("io.netty.noPreferDirect", "false"); // 配置Bootstrap时指定分配器 bootstrap.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT); ``` ⚠️需配合内存监控工具(如Android Profiler)检测Native内存泄漏 #### 2. 优化堆缓冲区性能 ```java // 使用池化堆缓冲区分配器 bootstrap.option(ChannelOption.ALLOCATOR, new PooledByteBufAllocator(true)); // true表示使用堆内存 // 调整内存池参数 PooledByteBufAllocator allocator = new PooledByteBufAllocator( true, // 堆内存 3, // 最小页大小=2^3=8KB 3, // 最大页数 8192, // 缓存区最大容量 16 // 缓存区数量 ); ``` #### 3. 混合模式配置(推荐) ```java // 根据API等级动态选择 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { // Android 8.0+改进的DirectBuffer支持 bootstrap.option(ChannelOption.ALLOCATOR, new PooledByteBufAllocator(false)); } else { // 旧版本使用池化堆内存 bootstrap.option(ChannelOption.ALLOCATOR, new PooledByteBufAllocator(true)); } ``` ### 三、高级优化技巧 1. **内存对齐补偿**: $$ alignment = 4 \times \lceil \frac{bufferSize}{4096} \rceil $$ 在分配缓冲区时增加填充字节,解决ARMv7架构的内存对齐问题 2. **JNI临界区优化**: ```c // 本地方法中改用GetPrimitiveArrayCritical jbyte* data = (*env)->GetPrimitiveArrayCritical(env, byteArray, NULL); // 快速访问数据 (*env)->ReleasePrimitiveArrayCritical(env, byteArray, data, 0); ``` 3. **内存回收监控**: ```xml <!-- 在AndroidManifest.xml中启用严格模式 --> <application android:debuggable="true" android:vmSafeMode="enabled"> </application> ``` ### 四、兼容性测试要点 1. **覆盖测试设备**: - ARMv7/ARMv8/x86架构 - API 21-33不同Android版本 - 2GB/4GB/6GB内存配置 2. **压力测试指标**: $$ \text{内存泄漏率} = \frac{\sum \Delta NativeMem}{\sum \Delta HeapMem} \times 100\% $$ 建议控制在<5%为可接受范围
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值