Java NIO channel
channel(通道),byteBuffer(缓冲区),selector(io多路复用),通道FileChannel,SocketChannel的transferTo,transferFrom,MappedByteBuffer实现了零拷贝。
JVM调操作系统方法,read,write,都可以送字节数组。
Java对从操作系统写入和读取的字符数组做了转化为基本数据类型。
channel
表示可以执行IO操作的对象
常用方法:
read(ByteBuffer dst):从通道读取数据到缓冲区。
write(ByteBuffer src):将缓冲区中的数据写入通道。
close():关闭通道。
isOpen():检查通道是否打开。
byteBuffer
缓冲区
ByteBuffer类,
allocate方法,分配堆内内存;
allocateDirect方法,分配堆外内存;
子类MappedByteBuffer,映射虚拟内存;
网络编程
IO多路复用
selector
SelectionKey 通道的状态和兴趣操作(accept,connect,read,write),SelectionKey包含了该通道与
Selector` 之间的关联信息
SelectableChannel
在 Java NIO 中,Selector
和 SelectionKey
是实现非阻塞 I/O 的核心组件,它们共同工作以允许一个或多个线程管理多个通道(如 SocketChannel
、ServerSocketChannel
等)。Selector
用于监控多个 SelectableChannel
对象的 I/O 操作(读、写、连接等),而 SelectionKey
则表示每个注册到 Selector
上的通道的状态和兴趣操作。下面详细解释 SelectionKey
的作用:
1. 注册通道与选择器
当一个 SelectableChannel
被注册到 Selector
上时,会返回一个 SelectionKey
对象。这个 SelectionKey
包含了该通道与 Selector
之间的关联信息。
SelectableChannel channel = ...; // 例如 SocketChannel 或 ServerSocketChannel
Selector selector = Selector.open();
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
2. 兴趣集 (Interest Set)
SelectionKey
的兴趣集指定了我们希望 Selector
监控的 I/O 操作类型。常见的兴趣操作包括:
- OP_ACCEPT:监听新连接的请求(仅适用于
ServerSocketChannel
)。 - OP_CONNECT:监听连接完成事件(适用于客户端
SocketChannel
的异步连接)。 - OP_READ:监听可读事件。
- OP_WRITE:监听可写事件。
你可以通过 interestOps()
方法设置或修改兴趣集:
key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
3. 就绪集 (Ready Set)
SelectionKey
的就绪集表示当前通道上哪些 I/O 操作已经准备好。每次 Selector
进行选择(调用 select()
或 selectNow()
)后,它会更新所有注册的 SelectionKey
的就绪集。你可以通过 readyOps()
方法检查哪些操作已经准备好:
int readySet = key.readyOps();
if ((readySet & SelectionKey.OP_READ) == SelectionKey.OP_READ) {// 通道已准备好读取
}
if ((readySet & SelectionKey.OP_WRITE) == SelectionKey.OP_WRITE) {// 通道已准备好写入
}
4. 附加对象 (Attached Object)
SelectionKey
允许你将任意对象附加到键上,这通常用于存储与通道相关的上下文信息。你可以使用 attach()
和 attachment()
方法来设置和获取附加对象:
// 设置附加对象
key.attach(someObject);// 获取附加对象
Object attachedObject = key.attachment();
5. 取消注册 (Cancelling the Registration)
如果你不再需要 Selector
监控某个通道,可以调用 cancel()
方法取消注册。这会使得 SelectionKey
在下次 select()
调用时被移除:
key.cancel();
6. 通道和选择器的访问
SelectionKey
提供了方法来访问与其关联的通道和选择器:
- channel():返回与
SelectionKey
关联的SelectableChannel
。 - selector():返回与
SelectionKey
关联的Selector
。
SelectableChannel channel = key.channel();
Selector selector = key.selector();
7. 迭代选择的键
当你调用 Selector.select()
或 selectNow()
后,Selector
会返回一个包含所有就绪键的集合。你可以遍历这些键来处理就绪的 I/O 操作:
Selector selector = Selector.open();
// 注册通道...while (true) {// 等待通道准备就绪selector.select();// 获取所有就绪的 SelectionKeySet<SelectionKey> selectedKeys = selector.selectedKeys();Iterator<SelectionKey> iterator = selectedKeys.iterator();while (iterator.hasNext()) {SelectionKey key = iterator.next();if (key.isAcceptable()) {// 处理接受连接} else if (key.isReadable()) {// 处理读取数据} else if (key.isWritable()) {// 处理写入数据}// 移除已处理的键iterator.remove();}
}
总结
SelectionKey
在 Java NIO 中扮演着至关重要的角色,它不仅记录了通道的兴趣操作和就绪状态,还提供了附加对象的功能,便于开发者在处理 I/O 事件时携带额外的上下文信息。通过 SelectionKey
,你可以高效地管理和响应多个通道的 I/O 操作,从而实现高性能的网络应用程序。
Selector
Selector
是 Java NIO(New Input/Output)库中的一个重要组件,它允许单个线程管理多个 SelectableChannel
(如 SocketChannel
、ServerSocketChannel
等)。通过 Selector
,你可以监控多个通道的 I/O 事件(如连接、读取、写入等),并在这些事件发生时得到通知。这使得编写高效的、非阻塞的网络应用程序成为可能,特别是在需要处理大量并发连接的情况下。
1. 创建 Selector
要使用 Selector
,首先需要创建一个实例。可以通过调用 Selector.open()
方法来创建一个新的 Selector
:
Selector selector = Selector.open();
2. 注册通道
要让 Selector
监控某个 SelectableChannel
,你需要将该通道注册到 Selector
上,并指定你感兴趣的 I/O 操作(称为“兴趣集”)。每个注册的通道会返回一个 SelectionKey
,这个键包含了通道的状态和相关信息。
// 创建一个 ServerSocketChannel
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false); // 设置为非阻塞模式// 绑定端口
serverChannel.bind(new InetSocketAddress(8080));// 将通道注册到 Selector 上,并指定兴趣操作为 OP_ACCEPT
SelectionKey key = serverChannel.register(selector, SelectionKey.OP_ACCEPT);
3. 选择就绪的通道
Selector
提供了几个方法来选择已经准备好进行 I/O 操作的通道:
select()
:阻塞当前线程,直到至少有一个通道准备好了所注册的操作。select(long timeout)
:阻塞当前线程,最多等待指定的时间(以毫秒为单位),如果在超时时间内没有任何通道准备好,则返回。selectNow()
:立即返回,不阻塞,检查是否有任何通道准备好。
每次调用这些方法后,Selector
会更新其内部的状态,并返回准备好的通道数量。
int readyChannels = selector.select(); // 阻塞直到有通道准备好
// 或者
int readyChannels = selector.select(1000); // 最多等待1秒
// 或者
int readyChannels = selector.selectNow(); // 立即返回
4. 获取并处理就绪的键
Selector
的 selectedKeys()
方法返回一个包含所有已准备好通道的 SelectionKey
集合。你可以遍历这个集合,检查每个 SelectionKey
的就绪状态,并根据需要处理相应的 I/O 操作。
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectedKeys.iterator();while (iterator.hasNext()) {SelectionKey key = iterator.next();if (key.isAcceptable()) {// 处理新的连接请求ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();SocketChannel clientChannel = serverChannel.accept();clientChannel.configureBlocking(false);clientChannel.register(selector, SelectionKey.OP_READ);} else if (key.isReadable()) {// 处理读取数据SocketChannel clientChannel = (SocketChannel) key.channel();ByteBuffer buffer = ByteBuffer.allocate(1024);int bytesRead = clientChannel.read(buffer);if (bytesRead == -1) {// 客户端关闭连接clientChannel.close();} else {// 处理接收到的数据buffer.flip();while (buffer.hasRemaining()) {System.out.print((char) buffer.get());}buffer.clear();}} else if (key.isWritable()) {// 处理写入数据SocketChannel clientChannel = (SocketChannel) key.channel();ByteBuffer buffer = ByteBuffer.wrap("Hello, Client!".getBytes());clientChannel.write(buffer);}// 移除已处理的键iterator.remove();
}
5. 关闭 Selector
当你不再需要 Selector
时,应该调用 close()
方法来释放相关的资源。这将关闭 Selector
并取消所有注册的通道。
selector.close();
6. 唤醒选择器
如果你在一个线程中调用了 select()
方法并且它正在阻塞,你可以通过调用 wakeup()
方法来唤醒它。这将使 select()
方法立即返回,即使没有通道准备好。
selector.wakeup();
7. 示例代码
以下是一个完整的示例,展示了如何使用 Selector
来处理多个客户端连接:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;public class NIOServer {public static void main(String[] args) throws IOException {// 创建 SelectorSelector selector = Selector.open();// 创建 ServerSocketChannel 并绑定端口ServerSocketChannel serverChannel = ServerSocketChannel.open();serverChannel.configureBlocking(false);serverChannel.bind(new InetSocketAddress(8080));// 注册 ServerSocketChannel 到 Selector,监听连接请求serverChannel.register(selector, SelectionKey.OP_ACCEPT);System.out.println("Server started on port 8080");while (true) {// 等待通道准备就绪selector.select();// 获取所有已准备好通道的 SelectionKeySet<SelectionKey> selectedKeys = selector.selectedKeys();Iterator<SelectionKey> iterator = selectedKeys.iterator();while (iterator.hasNext()) {SelectionKey key = iterator.next();if (key.isAcceptable()) {// 处理新的连接请求ServerSocketChannel server = (ServerSocketChannel) key.channel();SocketChannel clientChannel = server.accept();clientChannel.configureBlocking(false);clientChannel.register(selector, SelectionKey.OP_READ);System.out.println("New client connected: " + clientChannel.getRemoteAddress());} else if (key.isReadable()) {// 处理读取数据SocketChannel clientChannel = (SocketChannel) key.channel();ByteBuffer buffer = ByteBuffer.allocate(1024);int bytesRead = clientChannel.read(buffer);if (bytesRead == -1) {// 客户端关闭连接clientChannel.close();System.out.println("Client disconnected: " + clientChannel.getRemoteAddress());} else {// 处理接收到的数据buffer.flip();while (buffer.hasRemaining()) {System.out.print((char) buffer.get());}buffer.clear();}}// 移除已处理的键iterator.remove();}}}
}
总结
Selector
是 Java NIO 中实现高效、非阻塞 I/O 的关键组件。它允许一个线程同时管理多个通道,并在这些通道上发生 I/O 事件时得到通知。通过结合 SelectionKey
和 ByteBuffer
,你可以轻松地构建高性能的网络服务器和客户端应用程序。
Java nio的channel相关的api
Java NIO(New Input/Output)库提供了多种 Channel
类,用于非阻塞 I/O 操作和更高效的文件、网络通信。与传统的 I/O 流不同,Channel
是双向的,既可以读取数据也可以写入数据,并且支持直接缓冲区(DirectByteBuffer
)以提高性能。以下是 Java NIO 中常见的 Channel
类及其相关 API 的详细介绍:
1. FileChannel
FileChannel
用于对文件进行读写操作。它支持非阻塞模式,并且可以通过 transferTo()
和 transferFrom()
方法实现零拷贝。
-
创建 FileChannel:
- 通过
RandomAccessFile
、FileInputStream
或FileOutputStream
获取FileChannel
。
RandomAccessFile file = new RandomAccessFile("file.txt", "rw"); FileChannel channel = file.getChannel();
- 通过
-
读取和写入:
- 使用
read()
和write()
方法将数据从FileChannel
读取到ByteBuffer
或从ByteBuffer
写入FileChannel
。
ByteBuffer buffer = ByteBuffer.allocate(1024); int bytesRead = channel.read(buffer); // 读取数据到缓冲区 buffer.flip(); // 切换为读模式 channel.write(buffer); // 将缓冲区中的数据写入通道
- 使用
-
零拷贝:
transferTo()
:将文件内容直接传输到另一个WritableByteChannel
,如SocketChannel
。transferFrom()
:从ReadableByteChannel
将数据传输到FileChannel
。
long position = 0; long count = channel.size(); channel.transferTo(position, count, socketChannel); // 文件到套接字
-
映射文件:
map()
:将文件的一部分或全部映射到内存中,返回一个MappedByteBuffer
。
MappedByteBuffer mappedBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, fileSize);
transferTo():
将文件内容从 FileChannel 直接传输到另一个 WritableByteChannel(如 SocketChannel),而不需要将数据加载到应用程序的内存中。
语法:long transferTo(long position, long count, WritableByteChannel target)
适用于文件到网络套接字的高效传输。
transferFrom():
从 ReadableByteChannel(如 SocketChannel)将数据传输到 FileChannel,同样不需要将数据完全加载到应用程序的内存中。
语法:long transferFrom(ReadableByteChannel src, long position, long count)
适用于从网络套接字接收数据并写入文件。
2. SocketChannel
SocketChannel
用于 TCP 网络通信,支持非阻塞模式和异步连接。
-
创建 SocketChannel:
- 通过
SocketChannel.open()
创建一个新的SocketChannel
。
SocketChannel socketChannel = SocketChannel.open();
- 通过
-
连接服务器:
- 使用
connect()
方法连接到远程服务器。
InetSocketAddress address = new InetSocketAddress("localhost", 8080); socketChannel.connect(address);
- 使用
-
非阻塞模式:
- 调用
configureBlocking(false)
设置为非阻塞模式。
socketChannel.configureBlocking(false);
- 调用
-
读取和写入:
- 使用
read()
和write()
方法与远程服务器进行数据交换。
ByteBuffer buffer = ByteBuffer.allocate(1024); int bytesRead = socketChannel.read(buffer); // 读取数据 buffer.flip(); // 切换为读模式 socketChannel.write(buffer); // 发送数据
- 使用
-
关闭连接:
- 调用
close()
方法关闭SocketChannel
。
socketChannel.close();
- 调用
3. ServerSocketChannel
ServerSocketChannel
用于监听传入的 TCP 连接请求,通常与 Selector
一起使用来处理多个客户端连接。
-
创建 ServerSocketChannel:
- 通过
ServerSocketChannel.open()
创建一个新的ServerSocketChannel
。
ServerSocketChannel serverChannel = ServerSocketChannel.open();
- 通过
-
绑定端口:
- 使用
bind()
方法绑定到指定的本地地址和端口。
InetSocketAddress address = new InetSocketAddress(8080); serverChannel.bind(address);
- 使用
-
接受连接:
- 使用
accept()
方法接受新的客户端连接,返回一个新的SocketChannel
。
SocketChannel clientChannel = serverChannel.accept();
- 使用
-
非阻塞模式:
- 调用
configureBlocking(false)
设置为非阻塞模式。
serverChannel.configureBlocking(false);
- 调用
-
注册到 Selector:
- 将
ServerSocketChannel
注册到Selector
上,监听OP_ACCEPT
事件。
SelectionKey key = serverChannel.register(selector, SelectionKey.OP_ACCEPT);
- 将
4. DatagramChannel
DatagramChannel
用于 UDP 网络通信,支持非阻塞模式和异步发送/接收数据包。
-
创建 DatagramChannel:
- 通过
DatagramChannel.open()
创建一个新的DatagramChannel
。
DatagramChannel datagramChannel = DatagramChannel.open();
- 通过
-
绑定端口:
- 使用
bind()
方法绑定到指定的本地地址和端口。
InetSocketAddress address = new InetSocketAddress(8080); datagramChannel.bind(address);
- 使用
-
非阻塞模式:
- 调用
configureBlocking(false)
设置为非阻塞模式。
datagramChannel.configureBlocking(false);
- 调用
-
发送和接收数据:
- 使用
send()
和receive()
方法发送和接收数据包。
ByteBuffer buffer = ByteBuffer.wrap("Hello, Client!".getBytes()); InetSocketAddress remoteAddress = new InetSocketAddress("localhost", 8080); datagramChannel.send(buffer, remoteAddress); // 发送数据buffer.clear(); InetSocketAddress senderAddress = (InetSocketAddress) datagramChannel.receive(buffer); // 接收数据
- 使用
-
注册到 Selector:
- 将
DatagramChannel
注册到Selector
上,监听OP_READ
和OP_WRITE
事件。
SelectionKey key = datagramChannel.register(selector, SelectionKey.OP_READ);
- 将
5. AsynchronousFileChannel
AsynchronousFileChannel
提供了异步文件 I/O 的功能,可以在不阻塞主线程的情况下执行文件读写操作。
-
创建 AsynchronousFileChannel:
- 通过
AsynchronousFileChannel.open()
创建一个新的AsynchronousFileChannel
。
Path path = Paths.get("file.txt"); AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(path, StandardOpenOption.READ);
- 通过
-
异步读取和写入:
- 使用
read()
和write()
方法异步读取和写入文件内容,返回一个Future
对象。
ByteBuffer buffer = ByteBuffer.allocate(1024); Future<Integer> readResult = fileChannel.read(buffer, 0); // 异步读取 Future<Integer> writeResult = fileChannel.write(buffer, 0); // 异步写入
- 使用
-
完成处理器:
- 可以使用
CompletionHandler
来处理异步操作的结果。
fileChannel.read(buffer, 0, buffer, new CompletionHandler<Integer, ByteBuffer>() {@Overridepublic void completed(Integer result, ByteBuffer attachment) {System.out.println("Read " + result + " bytes");}@Overridepublic void failed(Throwable exc, ByteBuffer attachment) {System.err.println("Read failed: " + exc.getMessage());} });
- 可以使用
6. Pipe
Pipe
是一种特殊的通道,用于在同一个 JVM 内的不同线程之间传递数据。它由两个部分组成:SinkChannel
(写入端)和 SourceChannel
(读取端)。
-
创建 Pipe:
- 通过
Pipe.open()
创建一个新的Pipe
。
Pipe pipe = Pipe.open();
- 通过
-
获取 SinkChannel 和 SourceChannel:
- 使用
sink()
和source()
方法分别获取写入端和读取端。
Pipe.SinkChannel sinkChannel = pipe.sink(); Pipe.SourceChannel sourceChannel = pipe.source();
- 使用
-
读取和写入:
- 使用
write()
和read()
方法在SinkChannel
和SourceChannel
之间传递数据。
ByteBuffer buffer = ByteBuffer.wrap("Hello, Pipe!".getBytes()); sinkChannel.write(buffer); // 写入数据 buffer.clear(); sourceChannel.read(buffer); // 读取数据
- 使用
总结
Java NIO 提供了丰富的 Channel
类,涵盖了文件、TCP、UDP 以及异步 I/O 操作。这些类不仅支持非阻塞模式,还提供了高效的零拷贝和异步操作机制,使得开发者可以构建高性能的网络应用程序和文件处理系统。选择合适的 Channel
类取决于具体的应用场景和需求,例如是否需要处理大量并发连接、是否需要高效的数据传输等。
内存映射文件(MappedByteBuffer)在哪里分配缓冲区?和操作系统的page cache 的交互过程是啥?
内存映射文件(MappedByteBuffer
)的缓冲区并不是像普通 ByteBuffer
那样直接在 JVM 的堆空间中分配,而是通过操作系统的虚拟内存管理机制来实现的。具体来说,MappedByteBuffer
的缓冲区是通过操作系统提供的内存映射功能分配的,它实际上映射到了操作系统的 页缓存(Page Cache) 中。下面我们将详细解释 MappedByteBuffer
的分配机制以及它与操作系统页缓存的交互过程。
1. MappedByteBuffer
的缓冲区分配
当调用 FileChannel.map()
方法时,JVM 会请求操作系统将文件的某个区域映射到进程的虚拟地址空间中。这个映射过程由操作系统的内存管理单元(MMU)处理,具体步骤如下:
-
虚拟地址映射:操作系统会在进程的虚拟地址空间中为文件的指定区域分配一段虚拟地址。这段虚拟地址并不立即对应物理内存中的实际页面,而是一个虚拟映射。
-
按需加载:只有当程序访问这段虚拟地址中的数据时,操作系统才会根据需要将对应的文件页面从磁盘加载到物理内存中。这个过程称为 缺页中断(Page Fault)。操作系统会检查页表,找到对应的磁盘位置,然后将数据加载到物理内存中,并更新页表以记录新的映射关系。
-
共享页缓存:加载到物理内存中的页面会被存储在操作系统的 页缓存(Page Cache) 中。页缓存是操作系统用于缓存文件数据的内存区域,它可以加速对文件的读写操作。多个进程可以通过页缓存共享同一份文件数据,从而提高效率。
因此,MappedByteBuffer
的缓冲区并不是直接在 JVM 的堆空间中分配的,而是通过操作系统的虚拟内存管理机制映射到物理内存中的页缓存。这意味着 MappedByteBuffer
的内容实际上是存储在操作系统管理的物理内存中,而不是 JVM 的堆中。
2. MappedByteBuffer
与操作系统页缓存的交互过程
MappedByteBuffer
与操作系统页缓存之间的交互主要体现在以下几个方面:
2.1 读取操作
-
按需加载:当你通过
MappedByteBuffer
读取文件内容时,JVM 会通过虚拟地址访问数据。如果该虚拟地址对应的页面尚未加载到物理内存中,操作系统会触发 缺页中断,并将所需的页面从磁盘加载到页缓存中。 -
缓存命中:如果该页面已经在页缓存中(即之前已经被加载过),操作系统可以直接从页缓存中返回数据,而不需要再次从磁盘读取。这大大提高了读取速度,因为访问物理内存的速度远快于访问磁盘。
-
透明性:对于应用程序来说,读取
MappedByteBuffer
的过程是透明的,你只需要像操作普通内存一样读取数据,而不需要显式地进行 I/O 操作。操作系统会自动处理页面的加载和缓存。
2.2 写入操作
-
写时复制(Copy-On-Write):如果你使用
READ_WRITE
或PRIVATE
模式对MappedByteBuffer
进行写操作,操作系统会根据模式的不同处理写入行为:READ_WRITE
模式:写入的数据会直接反映到文件中。操作系统会将修改后的页面标记为脏页(Dirty Page),并在适当的时候将这些页面同步回磁盘。你可以通过调用force()
方法强制将修改同步到磁盘。PRIVATE
模式:写入的数据不会影响原始文件。操作系统会使用 写时复制 机制,在你第一次写入某个页面时,操作系统会创建该页面的副本,并将修改应用到副本上,而原始文件保持不变。
-
脏页管理:当
MappedByteBuffer
中的数据被修改后,操作系统会将这些页面标记为脏页。脏页会保留在页缓存中,直到操作系统决定将其写回到磁盘。通常,操作系统会在系统空闲时或内存压力较大时将脏页同步回磁盘。 -
同步到磁盘:你可以通过调用
MappedByteBuffer.force()
方法显式地将脏页同步到磁盘。这确保了文件的修改被持久化,避免在系统崩溃时丢失数据。
2.3 换出和换入
-
换出(Swap Out):当物理内存不足时,操作系统可能会将不常用的页面换出到磁盘上的交换文件(Swap File)。这包括
MappedByteBuffer
映射的页面。换出的过程是透明的,应用程序不会感知到这一变化。当程序再次访问这些页面时,操作系统会重新将它们从交换文件加载回物理内存。 -
换入(Swap In):当程序访问一个已经被换出的页面时,操作系统会触发 缺页中断,并将该页面从交换文件加载回物理内存。这个过程与从磁盘加载文件页面类似,但性能稍差,因为交换文件通常是位于较慢的存储设备上。
2.4 内存回收
-
自动回收:当
MappedByteBuffer
对应的FileChannel
被关闭时,操作系统会自动回收该映射的虚拟地址空间。然而,操作系统并不会立即释放物理内存中的页面,除非这些页面不再被任何进程使用。这意味着即使MappedByteBuffer
已经被垃圾回收,操作系统仍然可能保留这些页面在页缓存中,以备后续使用。 -
手动清理:在某些情况下,你可能希望显式地清理
MappedByteBuffer
映射的内存。Java 8 及之后版本提供了一个sun.misc.Cleaner
类,可以通过反射调用其clean()
方法来强制清理MappedByteBuffer
。然而,这种方法是非标准的,且不推荐在生产环境中使用。
3. MappedByteBuffer
与 JVM 堆空间的关系
MappedByteBuffer
的缓冲区并不在 JVM 的堆空间中分配,因此它的大小不受 JVM 堆大小的限制。相反,MappedByteBuffer
的大小取决于操作系统的虚拟内存管理和可用的物理内存。这意味着你可以映射非常大的文件,而不会导致 JVM 堆溢出(OutOfMemoryError
)。
然而,虽然 MappedByteBuffer
不占用 JVM 堆空间,但它仍然会占用操作系统的虚拟地址空间。在 32 位系统上,虚拟地址空间是有限的(通常为 4GB),因此映射过大的文件可能会导致虚拟地址空间耗尽。在 64 位系统上,虚拟地址空间非常大,这个问题较少发生。
4. 优点与注意事项
优点:
- 高性能:通过利用操作系统的页缓存和按需加载机制,
MappedByteBuffer
可以显著提高文件 I/O 的性能,尤其是在处理大文件时。 - 简化编程模型:你可以像操作普通内存一样访问文件内容,代码更加简洁。
- 异步加载:操作系统会在需要时自动将文件的数据从磁盘加载到内存,而不是一次性加载整个文件,节省了内存并提高了响应速度。
- 支持随机访问:你可以直接访问文件的任意位置,而不需要从头开始读取。
注意事项:
- 内存使用:虽然
MappedByteBuffer
不占用 JVM 堆空间,但它会占用操作系统的虚拟地址空间。在 32 位系统上,虚拟地址空间是有限的,因此需要注意不要映射过大的文件。 - 持久性问题:在某些情况下,操作系统可能不会立即将修改后的数据写回到磁盘,这可能导致数据丢失。你可以使用
force()
方法强制将修改同步到磁盘。 - 安全性问题:由于
MappedByteBuffer
允许直接访问文件内容,因此在多线程环境中使用时需要注意线程安全问题,避免多个线程同时修改同一部分数据。 - 内存回收:
MappedByteBuffer
对应的内存映射区域不会自动释放,即使MappedByteBuffer
对象被垃圾回收。为了确保资源正确释放,建议在不再需要时显式地关闭相关的FileChannel
。
5. 总结
MappedByteBuffer
是 Java NIO 中一个强大的工具,它通过内存映射文件技术提供了高效的文件 I/O 操作。MappedByteBuffer
的缓冲区并不是直接在 JVM 的堆空间中分配的,而是通过操作系统的虚拟内存管理机制映射到物理内存中的页缓存。操作系统负责管理页面的加载、换入换出和脏页同步,确保文件数据的高效访问和持久化。理解 MappedByteBuffer
与操作系统页缓存的交互过程,可以帮助开发者更好地优化文件 I/O 性能,并避免潜在的问题。
linux mmap()+
write()` 的组合是一种实现零拷贝(或称为“半零拷贝”)的方法
mmap()
+ write()
的组合是一种实现零拷贝(或称为“半零拷贝”)的方法,它通过将文件内容映射到进程的地址空间,然后使用 write()
系统调用来发送这些数据。这种方式减少了从内核空间到用户空间的数据复制次数,但仍然需要一次从用户空间到套接字缓冲区的复制。下面是这个过程的一个简化图示:
+---------------------+ +---------------------+
| | | |
| 用户空间 (User) | | 内核空间 (Kernel) |
| | | |
| +--------------+ | | +--------------+ |
| | mmap() 映射 | | | | 文件缓存 | |
| | 区域 |<--+---------+->| (Page Cache) | |
| +--------------+ | | +--------------+ |
| | | |
| +--------------+ | | +--------------+ |
| | write() | | | | 套接字缓冲区 | |
| | 发送数据 |---+-------->+->| (Socket Buf) | |
| +--------------+ | | +--------------+ |
| | | |
+---------------------+ +---------------------+
具体步骤说明
-
文件映射 (
mmap()
):- 应用程序调用
mmap()
系统调用,将文件的一部分或全部映射到进程的虚拟地址空间。 - 这个操作不会立即将文件内容加载到内存中,而是创建了一个虚拟内存区域,当应用程序访问这个区域时,操作系统会按需将文件内容加载到物理内存(页缓存)中。
- 应用程序调用
-
数据传输 (
write()
):- 应用程序可以直接访问
mmap()
创建的内存区域,读取文件内容。 - 然后,应用程序调用
write()
系统调用,将这些数据写入一个套接字或其他文件描述符。 - 在这一步,数据会被从用户空间复制到内核空间的套接字缓冲区中,准备发送给网络接口卡(NIC)或其他目的地。
- 应用程序可以直接访问
图解中的箭头表示
- 虚线箭头 表示
mmap()
操作创建了用户空间和内核空间之间的映射关系,但并不立即发生数据复制。 - 实线箭头 表示实际的数据复制操作,即当
write()
被调用时,数据从用户空间被复制到内核空间的套接字缓冲区。
注意事项
mmap()
和write()
组合虽然减少了部分数据复制,但并不是完全的零拷贝,因为write()
仍然需要将数据从用户空间复制到内核空间。- 对于大文件或大量数据的传输,这种方法可以显著减少CPU的负担和上下文切换,提高性能。
- 使用
mmap()
时需要注意文件大小和系统资源的限制,以及正确处理内存映射区域的同步问题。
如果你想要实现真正的零拷贝,可以考虑使用 sendfile()
或者 splice()
等更先进的系统调用,它们可以在某些情况下避免用户空间与内核空间之间的数据复制。
相关文章:

Java NIO channel
channel(通道),byteBuffer(缓冲区),selector(io多路复用),通道FileChannel,SocketChannel的transferTo,transferFrom,MappedByteBuffer实现了零拷贝。 JVM调操作系统方法,read,write,都可以送字…...

智能交通(8)——腾讯开悟智能交通信号灯调度赛道
本文档用于记录参加腾讯开悟智能信号灯调度赛道的模型优化过程。官方提供了dqn和target_dqn算法,模型的优化在官方提供的代码基础上进行。最终排名是在榜单16,没能进入最后的决赛。 一.赛题介绍 赛题简介:在本地赛题中,参赛团队…...

ip所属地址是什么意思?怎么改ip地址归属地
在数字化时代,IP地址作为网络设备的唯一标识符,不仅关乎设备间的通信,还涉及到用户的网络身份与位置信息。IP所属地址,即IP地址的归属地,通常反映了设备连接互联网时的地理位置。本文将深入解析IP所属地址的含义&#…...

攻防世界 ctf刷题 新手区1-10
unserialize3 因为我上个笔记写了 php返序列化 所以先趁热打铁 看这个题目名字 我们就知道是 反序列化呀 因为flag有值所以 我们先输个 111 看看有没有线索 没线索但是这边 有个发现就是他是使用get方式传参的 可能他会把我们的输入 进行传入后台有可能进行反…...

Node做一个自动删除指定文件和文件夹工具
node14 可以搭配脚手架工具实现自动实现删除 // 引入path模块,用于处理文件路径 const path require(path); // 引入fs模块的promises API,用于异步文件操作 const fs2 require(fs).promises; // 引入fs模块,用于同步文件操作 const fs …...

陈若尧新歌《一来二去》陆续登陆全球音乐平台
由青年演员,歌手陈若尧带来的全新创作单曲《一来二去》由索尼音乐发行,于2024年11月18日陆续全球上线。这也是陈若尧与索尼音乐合作的第一首单曲。探索古典风格与流行音乐的新结合。歌曲上线不久,就因优美抒情的动人旋律,诗意而意味深远的歌词…...

【Docker】针对开发环境、测试环境、生产环境如何编排?
目录 一、引言 二、Docker Compose 文件基础 三、针对不同环境的 Docker 编排 开发环境 测试环境 生产环境 四、配置文件全局变量的编写 五、总结 一、引言 在软件开发和部署的过程中,不同的环境有着不同的需求和配置。Docker 作为一种强大的容器化技术&…...

小程序项目的基本组成结构
分类介绍 项目根目录下的文件及文件夹 pages文件夹 用来存放所有小程序的页面,其中每个页面都由4个基本文件组成,它们分别是: .js文件:页面的脚本文件,用于存放页面的数据、事件处理函数等 .json文件:…...

001-mysql安装
[rootcentos701 ~]# hostname -I 10.0.0.200 172.17.0.1 [rootcentos701 ~]# hostname centos701 [rootcentos701 ~]# rpm -qa | grep mariadb [rootcentos701 ~]# rpm -e --nodeps mariadb-libs-5.5.65-1.el7.x86_64 [rootcentos701 ~]# useradd mysql -s /sbin/nologin #创建…...

预训练模型与ChatGPT:自然语言处理的革新与前景
目录 一、ChatGPT整体背景认知 (一)ChatGPT引起关注的原因 (二)与其他公司的竞争情况 二、NLP学习范式的发展 (一)规则和机器学习时期 (二)基于神经网络的监督学习时期 &…...

高通---Camera调试流程及常见问题分析
文章目录 一、概述二、Camera配置的整体流程三、Camera的代码架构图四、Camera数据流的传递五、camera debug FAQ 一、概述 在调试camera过程中,经常会遇到各种状况,本篇文章对camera调试的流程进行梳理。对常见问题的提供一些解题思路。 二、Camera配…...

【冷冻电镜】RELION5.0使用教程总结
准备数据集: A test data set composed of 5 tomograms of immature HIV-1 dMACANC VLPs, which is available at EMPIAR-10164. 原始倾斜系列数据需要是单独的影片或单独的运动校正图像,但不是组合倾斜系列堆栈。 mdoc 文件包含每个倾斜系列的元数据。…...

【Maven系列】深入解析 Maven 镜像配置
前言 Maven 是一个流行的 Java 项目管理和构建工具,可以自动化构建项目、管理依赖、生成报告等。在Maven构建项目时,通常经常需要下载各种依赖。默认情况下,Maven 会从中央仓库下载这些依赖,但在某些情况下,这个过程可…...

优质翻译在美国电子游戏推广中的作用
美国作为世界上最大的视频游戏市场之一,为寻求全球成功的游戏开发商提供了无与伦比的机会。然而,美国市场的文化和语言多样性使其成为一个复杂的导航景观。高质量的翻译在弥合开发者和这些充满活力的观众之间的差距方面发挥着关键作用,确保游…...

数据结构---栈(Stack)
1. 简介 栈(Stack)是计算机科学中的一种抽象数据类型,它遵循特定的操作顺序,即后进先出(Last In First Out,LIFO)。这意味着最后添加到栈中的元素将是第一个被移除的。栈的基本操作通常包括&am…...

【全网最新】若依管理系统基于SpringBoot的前后端分离版本开发环境配置
目录 提前准备: 下载源代码 设置依赖 设置后台连接信息 运行后台 运行前端 安装npm依赖 启动前端 登录网页客户端 提前准备: 1、安装mysql 5以上就可以。 2、安装redis. 3、安装npm npm下载地址:https://nodejs.org/dist/v22.12…...

limit(0,10)和limit(10,10)有什么区别吗?
在SQL查询中,LIMIT子句用于限制查询结果的数量。LIMIT子句通常有两种形式: LIMIT offset, countLIMIT count 这里的offset表示从哪一条记录开始选取,count表示选取多少条记录。 LIMIT(0,10):这种形式的LIMIT子句表示从第一条记录…...

grpc与rpcx的区别
什么是微服务?rpc架构的主要区别rpcx与grpc的区别rpcx:grpc:为什么grpc要使用http2,为什么不适应http1或者http3?为什么grpc要使用proto而不是json或者其他数据格式? 为什么rpcx快,快多少?rpcx的具体性能指标与grpc比较: 什么是微服务? 整体功能通过多个程序实现,每个程序…...

基于XML的AOP开发
AOP 为 Aspect Oriented Programming 的缩写,意思为面向切面编程。 AOP相关术语: 目标对象(Target): 你要去代理的对象,可以理解为之前很单纯的那个对象。 代理对象(Proxy): 你把你那个单纯的对象给我,…...

pdf也算是矢量图——pdf大小调整--福昕pdf
有时候需要把pdf作为矢量图放到latex论文中,有时候需要裁剪掉空白的部分,就需要用福昕pdf进行编辑, 参考文章:福昕高级PDF编辑器裁切工具怎么用?裁切工具使用方法介绍_福昕PDF软件工具集 (foxitsoftware.cn)...

Web应用程序文件包含-Server2233-解析
B-6 Web应用程序文件包含 任务环境说明:服务器场景名称:Server2233...

AI开发: 知识图谱的初识,学会制作知识图谱- Python 机器学习
一、知识图谱的概念 知识图谱是一个通过图结构来表示和组织知识的工具,它将事物、概念和它们之间的关系以图的形式呈现出来,图中的节点代表实体(比如人物、地点、事件等),而边代表这些实体之间的各种关系(…...

Ubuntu Linux用户与组的管理
Ubuntu Linux操作系统- 第一弹 由猪猪侠开启Linux操作系统的学习 文章目录 前言Linux操作系统的发展Linux版本 Linux用户账户及其类型超级用户系统用户普通用户 Ubuntu超级用户权限与管理员Linux的超级用户权限解决方案Ubuntu管理员sudo命令su命令Ubuntu启用root登录 组账户及其…...

算力100问☞第32问:密集计算的关键技术有哪些?
1、高性能处理器和图形处理器 高性能处理器和图形处理器作为计算系统中的核心组件,发挥着至关重要的作用。 高性能处理器是密集计算的基础。它们采用先进的制程技术和架构设计,能够提供更高的时钟频率和更多的核心数量,从而实现更快的计算速…...

Rust : 生成日历管理markdown文件的小工具
需求: 拟生成以下markdown管理小工具,这也是我日常工作日程表。 可以输入任意时间段,运行后就可以生成以上的markdown文件。 一、toml [package] name "rust-workfile" version "0.1.0" edition "2021"[d…...

【并集查询】.NET开源 ORM 框架 SqlSugar 系列
.NET开源 ORM 框架 SqlSugar 系列 【开篇】.NET开源 ORM 框架 SqlSugar 系列【入门必看】.NET开源 ORM 框架 SqlSugar 系列【实体配置】.NET开源 ORM 框架 SqlSugar 系列【Db First】.NET开源 ORM 框架 SqlSugar 系列【Code First】.NET开源 ORM 框架 SqlSugar 系列【数据事务…...

基于单片机的智能农田灌溉节水系统设计及应用
摘 要 : 针对传统的灌溉方法浪费水资源节水系统设计。该系统从节水角度出发,对传感器和主电路进行了设计,主要采集灌溉地的湿度与温度数据,根据测量土壤中的温度与湿度作为主要参数,对农田灌溉节水系统进行实时控制&am…...

jmeter如何导出中文版的测试报告?
文章目录 0、初始步骤:把报告模板换成中文形式1、首先添加一份聚合报告2、然后点开【聚合报告】3,生成报告3.1 选择【工具】-【generate HTML report】3.2 【generate HTML report】参数详解3.3 、最后点击 【generate report】直接生成。 声明ÿ…...

AIGC 与艺术创作:变革与机遇
在当今数字化时代,人工智能生成内容(AIGC)正以惊人的速度重塑着艺术创作的格局,为艺术家们带来了令人振奋的新机遇。 一.AIGC 的崛起与艺术领域的变革 随着人工智能技术的不断进步,AIGC 逐渐在艺术领域崭露头角。它依…...

【Axios】如何在Vue中使用Axios请求拦截器
✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,…...