Skip to content

IO 与 NIO 排查面试

本页把 IO/NIO 的常见线上问题和面试回答集中整理。详细原理请跳转到对应知识点页。

面试怎么学

mermaid
flowchart TD
    A["面试题"] --> B["先给结论"]
    B --> C["解释原理"]
    C --> D["说商业场景"]
    D --> E["补充坑点和排查"]
    E --> F["跳转知识点深入理解"]

不要只背“BIO 阻塞,NIO 非阻塞”。面试官真正想听的是:为什么阻塞会占线程、Selector 怎么减少线程、Buffer 指针怎么变化、生产中出了问题怎么查。

高频面试题

面试题标准回答原理跳转
字节流和字符流区别字节流处理原始二进制数据,适合图片、压缩包、网络报文;字符流处理文本,会涉及字符集编码和解码。文本读写必须显式指定编码,避免依赖平台默认编码。传统 IO缓冲与字符编码
为什么要用缓冲流缓冲流把多次小 IO 合并成较少的大 IO,减少系统调用和底层磁盘/网络访问次数。输出缓冲还需要 flushclose 才能确保缓冲数据写到底层。缓冲与字符编码
BIO、NIO、AIO 区别BIO 是同步阻塞,通常一个连接一个线程;NIO 常见用法是同步非阻塞,通过 Selector 一个线程监听多个 Channel;AIO 是异步 IO,操作完成后通知回调。IO总览Selector多路复用
NIO 三大组件Buffer 是缓冲区,Channel 是连接文件或网络的通道,Selector 是多路复用器,用来监听多个 Channel 的事件。NIO Buffer与ChannelSelector多路复用
Buffer 的 flip 做什么flip 把 Buffer 从写模式切换到读模式,本质是 limit=positionposition=0。写完数据要读之前必须 flipNIO Buffer与Channel
clearcompact 区别clear 重置指针,表示数据处理完可以重新写;compact 保留未读数据并切回写模式,适合半包未处理完的场景。NIO Buffer与Channel
Selector 为什么能处理多个连接Channel 配成非阻塞后注册到 Selector,Selector 监听 accept/read/write 等事件。线程阻塞在 select,有事件才处理,不需要每个连接一个线程。Selector多路复用
NIO 是不是异步Java NIO 常见网络用法是同步非阻塞,不是异步。读写不会一直阻塞,但数据拷贝仍由调用线程执行;AIO 才是异步完成后回调。Selector多路复用
OP_WRITE 为什么会导致 CPU 高OP_WRITE 表示底层发送缓冲区可写,大多数时候都可写。如果一直监听,会导致 Selector 不断返回写事件。应该只有存在未写完数据时才注册,写完后移除。Selector多路复用
什么是零拷贝零拷贝是减少用户态和内核态之间数据复制的技术,Java 常见方式有 FileChannel.transferTo/transferFrom、mmap、DirectBuffer。它不是完全没有复制,而是减少不必要复制。零拷贝与大文件
DirectBuffer 为什么会 OOMDirectBuffer 使用堆外直接内存,不受 -Xmx 直接限制,但受进程内存和 MaxDirectMemorySize 影响。创建过多、释放不及时、Netty ByteBuf 泄漏都可能导致 Direct buffer memory零拷贝与大文件JVM内存结构

线上问题排查地图

mermaid
flowchart TD
    A["IO/NIO 线上异常"] --> B{"现象是什么"}
    B -- "乱码" --> C["检查编码和响应头"]
    B -- "OOM" --> D{"堆还是直接内存"}
    B -- "CPU 高" --> E["检查 Selector 空轮询 / OP_WRITE / 死循环"]
    B -- "接口卡住" --> F["检查阻塞 IO / 下游慢 / 线程池满"]
    B -- "Too many open files" --> G["检查流、Socket、文件句柄泄漏"]
    B -- "大文件慢" --> H["检查分块、缓冲、磁盘、网络、限速"]
    C --> I["修正字符集为 UTF-8"]
    D --> J["堆 dump / DirectMemory / RSS"]
    E --> K["jstack / 线程热点 / Netty 版本"]
    F --> L["线程栈 / 超时 / 连接池"]
    G --> M["lsof / 资源关闭 / try-with-resources"]
    H --> N["压测和链路耗时"]

问题一:中文乱码

常见原因:

位置风险
文件读取使用平台默认编码
HTTP 响应Content-Type 没指定 charset
数据库连接客户端、连接、表字段编码不一致
CSV/Excel文件来源是 GBK,系统按 UTF-8 读

排查步骤:

  1. 确认文件真实编码。
  2. 确认读取代码是否显式指定字符集。
  3. 确认 HTTP 请求响应头。
  4. 确认数据库连接串和表字段编码。
  5. 确认日志系统编码。

标准回答:

text
乱码本质是编码和解码字符集不一致。文件里保存的是字节,程序读取时要按正确字符集解码。生产中我会统一使用 UTF-8,并且文件读写、HTTP 响应、数据库连接都显式声明编码,避免使用平台默认编码。

问题二:大文件 OOM

危险写法:

java
byte[] bytes = Files.readAllBytes(path);

排查思路:

  1. 看 OOM 类型:Java heap space 还是 Direct buffer memory
  2. 看代码是否一次性读文件到内存。
  3. 看是否批量导入时把所有行放入 List。
  4. 看是否上传/下载没有分块。
  5. 看 GC 日志和 heap dump。

正确设计:

mermaid
flowchart TD
    A["打开文件"] --> B["分块读取"]
    B --> C["处理当前块"]
    C --> D["释放或复用缓冲区"]
    D --> E{"读完了吗"}
    E -- "否" --> B
    E -- "是" --> F["关闭资源"]

问题三:Too many open files

这个错误表示进程打开的文件描述符太多。文件、Socket、管道都可能占用 fd。

常见原因:

原因说明
流未关闭异常路径没 close
Socket 未关闭客户端断开后服务端没释放
文件监听过多WatchService 或日志文件打开太多
系统限制太低Linux ulimit -n 太小

Java 代码优先检查:

java
try (InputStream in = Files.newInputStream(path)) {
    // read
}

系统层面可以查:

bash
lsof -p <pid> | wc -l
ulimit -n

Windows 上可以用 Process Explorer 或句柄查看工具。

问题四:NIO 服务 CPU 高

常见原因:

原因解释
一直监听 OP_WRITESocket 大多数时候可写,导致 select 不断返回
Selector 空轮询特定 JDK/系统问题或框架 bug
业务死循环事件循环线程里有错误循环
IO 线程做慢业务队列堆积,事件处理不及时

排查:

  1. top 找 CPU 高线程。
  2. 转换线程 ID 为十六进制。
  3. jstack 找对应线程栈。
  4. 看是否在 Selector 循环、业务循环或编解码逻辑。
  5. Netty 项目检查版本和是否有阻塞业务跑在 EventLoop。

问题五:接口一直卡住

可能不是 CPU 问题,而是线程在等 IO。

排查步骤:

mermaid
flowchart TD
    A["接口卡住"] --> B["jstack 查看线程状态"]
    B --> C{"卡在哪里"}
    C -- "Socket read" --> D["下游接口慢或没超时"]
    C -- "File read/write" --> E["磁盘慢或文件锁"]
    C -- "DB driver" --> F["数据库慢或连接池满"]
    C -- "Object.wait" --> G["线程池/锁/队列等待"]
    D --> H["设置连接超时和读取超时"]
    E --> I["检查磁盘 IO 和文件大小"]
    F --> J["查慢 SQL 和连接池"]
    G --> K["查并发设计"]

项目话术:

text
采集平台如果大量接口调用下游医院系统,线程池里的任务可能长期阻塞在 HTTP read 上。排查时我会先看线程栈,如果大量线程卡在 socketRead,就检查下游耗时、连接超时、读取超时和线程池队列,不能只加线程数。

面试回答模板

text
IO/NIO 我会从场景说起。普通文件读写和小并发连接,用传统 IO 更简单,重点是缓冲、编码、资源关闭和大文件分块。高并发长连接下,BIO 一个连接一个线程成本太高,所以使用 NIO 的 Channel、Buffer、Selector,一个线程监听多个连接事件。NIO 不是简单 API 替换,它要求理解 Buffer 指针、非阻塞读写、半包粘包和 OP_WRITE。生产中复杂网络服务一般用 Netty,排查时重点看线程栈、文件句柄、直接内存、编码和大文件内存占用。

本章小结

IO/NIO 不是孤立知识点。它和线程池、JVM、Netty、HTTP、数据库连接池、文件导入导出都有关。学会它的标准不是能背类名,而是遇到乱码、OOM、CPU 高、接口卡死、文件句柄泄漏时,知道该看哪里、为什么会这样、怎么改。