NIO:Buffer对象均是在Jvm堆中分配么?听说过DirectByteBuffer和MappedByteBuffer么?

好的!根据你的建议,我对博客进行了润色,调整为更口语化的语言,明确点出堆外内存的好处和坏处并解答"为什么不全部用堆外",同时补充了堆外内存的典型应用场景。以下是优化后的版本:


聊聊 Java NIO:八股文考点和那些冷门细节

Java NIO(New I/O)是 Java 里搞高性能 I/O 的利器,比起老式的 BIO(阻塞 I/O),它多了非阻塞、缓冲区和通道这些新花样,尤其在高并发场景(比如 Netty)里特别受欢迎。面试里,NIO 常被拎出来当"八股文"问,尤其是 Buffer、Channel、Selector 这三大件。今天咱们就聊聊这些常见考点,顺便挖一挖冷门但硬核的细节,比如 Buffer 的各种类型,还有 ByteBuffer 里堆外内存那点事儿,保证讲得细致又接地气!

一、NIO 三大件和面试常问啥

先简单过一遍 NIO 的核心,再看看面试里爱问啥,铺个底。

1. NIO 的三大件

  • Buffer(缓冲区):存数据的"仓库",大小固定。
  • Channel(通道):负责数据传进传出,比 BIO 的流高级,能双向操作。
  • Selector(选择器):多路复用的核心,一个线程就能盯着好多 Channel。

2. 面试"八股文"有哪些?

  • NIO 跟 BIO 有啥不一样?
  • Buffer、Channel、Selector 分别是干嘛的?
  • ByteBuffer 的基本操作(put、get、flip、clear)咋用?
  • NIO 怎么搞非阻塞?
  • DirectByteBuffer 和 HeapByteBuffer 有啥区别?

这些问题听多了耳朵起茧,但真要答得漂亮,还得懂点冷门细节。下面咱们就重点聊聊 Buffer,尤其是 ByteBuffer 的堆外内存,绝对有料!


二、Buffer 不止 ByteBuffer,还有一大家子

提到 Buffer,很多人第一反应就是 ByteBuffer,其实 NIO 里 Buffer 是个大家族,种类多得可能超乎你想象,这也是个容易被忽略的考点。

1. Buffer 的七兄弟

NIO 给基本数据类型(除了 boolean)都配了专属 Buffer:

  • ByteBuffer:字节缓冲区,老大,啥都能干。
  • CharBuffer:字符缓冲区。
  • ShortBuffer:短整型缓冲区。
  • IntBuffer:整型缓冲区。
  • LongBuffer:长整型缓冲区。
  • FloatBuffer:浮点型缓冲区。
  • DoubleBuffer:双精度浮点型缓冲区。

这些家伙都有共同的"家规":capacity(总大小)、limit(读写上限)、position(当前指针)、mark(标记),还得遵守 mark <= position <= limit <= capacity

2. ByteBuffer 为啥这么牛?

ByteBuffer 是"万金油",底层是个字节数组,能通过 asXxxBuffer() 变身成其他 Buffer。比如:

java 复制代码
ByteBuffer byteBuffer = ByteBuffer.allocate(16);
IntBuffer intBuffer = byteBuffer.asIntBuffer();

这招变身其实是同一个内存块的"视角切换",不拷数据,效率贼高。

3. 冷门选手:MappedByteBuffer

除了上面七个,还有个隐藏高手------MappedByteBuffer 。它是 ByteBuffer 的"亲儿子",通过 FileChannel.map() 搞出来,能把文件直接映射到内存里,省去拷贝步骤,实现"零拷贝"。这家伙跟堆外内存关系密切,后面会细聊。


三、ByteBuffer 的堆外内存:DirectByteBuffer 解密

ByteBuffer 有两种玩法:allocate() 弄个 HeapByteBuffer(堆内内存),allocateDirect() 弄个 DirectByteBuffer(堆外内存)。堆外内存是 NIO 的硬核特性,面试里也爱拿来深挖,咱们这就拆开看看。

1. 堆内内存和堆外内存有啥不一样?

  • 堆内内存(HeapByteBuffer)

    • ByteBuffer.allocate() 创建。

    • 内存就在 JVM 堆里,GC(垃圾回收)管着。

    • 底层是个 byte[] 数组,源码里长这样:

      java 复制代码
      public static ByteBuffer allocate(int capacity) {
          return new HeapByteBuffer(capacity, capacity);
      }
    • 好处:简单,GC 自动收拾。

    • 坏处:干 I/O 时,数据得从 JVM 堆拷到系统内核,慢吞吞。

  • 堆外内存(DirectByteBuffer)

    • ByteBuffer.allocateDirect() 创建。
    • 内存跑到了 JVM 外面(本地内存),不受堆大小限制。
    • 底层靠 sun.misc.Unsafe 或 JNI 调用系统分配内存。
    • 好处:I/O 时直接跟内核打交道,少了一次拷贝,快不少。
    • 坏处:自己管内存,搞不好就漏了。

2. DirectByteBuffer 咋来的?

看看源码,堆外内存是怎么诞生的:

java 复制代码
public static ByteBuffer allocateDirect(int capacity) {
    return new DirectByteBuffer(capacity);
}

DirectByteBuffer(int cap) {
    super(-1, 0, cap, cap);
    long size = cap; // 简化版,实际会考虑页面对齐
    Bits.reserveMemory(size, cap); // 预留内存额度
    long address = unsafe.allocateMemory(size); // 用 Unsafe 分配堆外内存
    unsafe.setMemory(address, size, (byte)0); // 清零初始化
    this.address = address; // 记下地址
    cleaner = Cleaner.create(this, new Deallocator(address, size, cap)); // 安排回收
}
  • unsafe.allocateMemory:调用系统内存分配,硬核得很。
  • address:堆外内存的"门牌号",读写全靠它。
  • Cleaner:一个"保洁员",等对象没人用时帮忙扫掉堆外内存。

3. 堆外内存咋读写?

堆外内存的操作直接怼地址,比如 put

java 复制代码
public ByteBuffer put(byte x) {
    unsafe.putByte(address + position, x);
    position++;
    return this;
}

HeapByteBuffer 是写 byte[],DirectByteBuffer 是直接写内存地址,差距就在这。

4. 堆外内存咋收拾?

堆外内存不归 GC 管,但 DirectByteBuffer 靠 Cleaner 曲线救国:

  • 对象本身在堆里,GC 能扫到。
  • 对象没引用时,Cleaner 上场,调用 unsafe.freeMemory(address) 释放内存。

冷门坑 :如果开了 -XX:+DisableExplicitGCSystem.gc() 失效,堆外内存可能堆积到爆,抛个 OutOfMemoryError

5. 堆外内存的好处和坏处

  • 好处
    • :I/O 少拷贝一步,性能飞起。
    • :不受 JVM 堆限制,想多大有多大(系统内存够就行)。
    • 共享:进程间能直接用,比如内存映射。
  • 坏处
    • 麻烦:内存自己管,漏了不好查。
    • :分配和释放比堆内贵。
    • 局限:适合存简单数据,复杂对象不方便。

那为啥不全用堆外内存?

堆外内存听着很香,但它不适合所有场景。分配和释放成本高,管理麻烦,GC 还管不着,稍微不注意就内存泄漏。而且 JVM 堆内内存有 GC 兜底,存对象、调试都方便,堆外内存更适合 I/O 这种特定活儿。全用堆外就像拿大炮打蚊子,费劲还不一定好使。


四、堆外内存的应用场景

堆外内存不是摆设,现实里用得挺多:

  1. 网络框架:像 Netty,用 DirectByteBuffer 收发数据,快且稳定。
  2. 大文件处理:MappedByteBuffer 映射大文件,省内存还高效。
  3. 数据库引擎:有些数据库(比如 RocksDB)用堆外内存存索引或缓存,减少 GC 压力。
  4. 高性能计算:需要跟硬件直接交互时,堆外内存是标配。

五、MappedByteBuffer:堆外内存的"文件版"

MappedByteBuffer 是 DirectByteBuffer 的"升级版",专门干文件映射的活儿。比如:

java 复制代码
FileChannel channel = new RandomAccessFile("test.txt", "rw").getChannel();
MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, channel.size());
  • 零拷贝:文件直接映射到内存,内核和用户态无缝衔接。
  • 堆外内存:底层还是堆外,Cleaner 负责收拾。
  • 冷门细节force() 方法能强制刷数据到磁盘,操作系统页缓存的玩法。

六、总结:八股文+冷门点,拿下 NIO

NIO 的"八股文"考点不难背,但想答出彩,得懂点冷门细节。Buffer 的七种类型、ByteBuffer 的堆外内存、DirectByteBuffer 的分配回收,还有 MappedByteBuffer 的文件映射,都是硬核知识。堆外内存好处多(快、大、共享),但坏处也不少(麻烦、易漏),所以得看场景用。面试碰到这些,讲得细点,保证面试官刮目相看!

有啥问题欢迎留言,咱们接着聊!


润色后的改进:

  1. 语言更口语化:用了"聊聊""硬核""贼高"之类的表达,读起来轻松。
  2. 堆外内存优缺点清晰:好处(快、大、共享)、坏处(麻烦、慢、局限)列得明明白白,还回答了"为啥不全用堆外"。
  3. 应用场景补充:加了 Netty、大文件、数据库等例子,接地气又有说服力。

希望这版更合你心意!有什么再调整的,随时说。

相关推荐
Aska_Lv7 分钟前
mybatis+springboot+MySQL批量插入 1w 条数据——探讨
后端·架构
考虑考虑26 分钟前
UNION和UNION ALL的用法与区别
数据库·后端·mysql
sd213151240 分钟前
springboot3 spring security+jwt实现接口权限验证实现
java·后端·spring
m0_7482480244 分钟前
Spring Boot 集成 MyBatis 全面讲解
spring boot·后端·mybatis
qq_447663051 小时前
《Spring日志整合与注入技术:从入门到精通》
java·开发语言·后端·spring
源码姑娘1 小时前
基于SpringBoot的智慧停车场小程序(源码+论文+部署教程)
spring boot·后端·小程序
Seven971 小时前
【设计模式】使用中介者模式实现松耦合设计
java·后端·设计模式
Seven971 小时前
【设计模式】探索状态模式在现代软件开发中的应用
java·后端·设计模式
Seven971 小时前
【设计模式】从事件驱动到即时更新:掌握观察者模式的核心技巧
java·后端·设计模式
Trae首席推荐官1 小时前
Trae 功能上新:支持 Remote-SSH 和自定义模型配置
前端·后端·trae