文章目录
JAVA NIO零拷贝实现
零拷贝通常通常指网络发送文件时,不需要把文件内容拷贝到用户空间,而直接在内核空间中传输到网络方式。通过减少不同内存区域的拷贝,减低CPU的消耗。
java NIO中**通道相当于操作系统的内核空间的缓冲区**,而**缓冲区对应的相当于操作系统的用户空间的用户缓冲区**。
-
通道是全双工的(双向运输),它即可能是读缓冲区,也可能是网络缓冲区。
-
缓冲区分为堆内存和堆外内存,这是通过malloc()分配出来的用户态内存。
java NIO中的SocketChannel和ServerSocketChannel类在实现网络通信时,通常会使用HeadBuffer来读取和解析数据包头部信息。在网络通信中,数据往往是分成若干个数据包进行传输,每个数据包都包含了头部信息和数据内容。
ByteBuffer headBuffer = ByteBuffer.allocate(4); // 创建一个长度为4的HeadBuffer
socketChannel.read(headBuffer); // 读取HeadBuffer中的数据
headBuffer.flip(); // 将HeadBuffer切换为读模式
int packetLength = headBuffer.getInt(); // 从HeadBuffer中读取数据包长度
ByteBuffer dataBuffer = ByteBuffer.allocate(packetLength); // 创建一个长度为packetLength的数据缓冲区
socketChannel.read(dataBuffer); // 从SocketChannel中读取剩余的数据内容
JDK8及之前使用替身缓冲区,在使用HeadBuffer读写数据时,为了避免缓冲区数据因为GC而丢失,NIO会先把HeadBuffer内部的数据拷贝到一个临时DirectBuffer中的本地内存,通过Unsafe类的copyMemory()调用,类似
memcpy()
。然后将临时生成的DirectBuffer内部的数据内存地址传给I/O调用函数,避免了再去访问Java对象处理I/O读写。JDK9及之后的版本,JDK使用共享缓冲区的机制避免HeadBuffer数据丢失问题。JDK会创建一个共享的DirectBuffer,HeadBuffer和DirectBuffer共享一块内存区域。
MappedByteBuffer
**基于内存映射**,继承ByteBuffer。FileChannel定义了一个map()方法,可以把一个文件从position位置开始的size大小的区域映射为内存映像文件。**mmap技术。**
内存映射文件是指将磁盘上的文件映射到进程的虚拟内存空间,使得进程可以像访问文件,无需进行读取或写入操作。
/*
* mode:限定内存映射区域堆内存映像文件的访问模式:只读、可读可写、写时拷贝
* position:文件映射的起始地址,对应内存映射区域的首地址
* size:文件映射的字节长度。对应MappedByteBuffer的大小
*/
public abstract MappedByteBuffer map(MapMode mode, long position,
long size)
throws IOException;
在使用 MappedByteBuffer 读写文件时,操作系统会将文件的数据读入内核缓冲区,在进程中生成一个内存映射区域,然后将该内存映射区域映射到进程的虚拟内存空间中,从而使进程可以像访问内存一样访问文件。
MappedByteBuffer新增的三个重要的方法:
-
fore():对于可读可写模式下的缓冲区,把对缓冲区内容的修改强制刷新到本地文件。
-
load():将缓冲区的内容载入到物理内存中,并返回这个缓冲区的引用。(内核缓冲区写入到物理内存中)。
-
isLoaded():如果缓冲区的内容在物理内存中,返回true。
map()
方法的底层实现原理:
-
通过本地方法map0为文件分配一块虚拟内存,作为它的内存映射区域,返回这块内存映射区域的起始地址。
-
通过(起始地址+偏移量)就可以获取指定内存的数据。
-
通过jni调用c语言的mmap64()对linux底层内核发出内存映射的调用。
MappedByteBuffer的特点和不足:
-
MappedByteBuffer 使用是堆外的虚拟内存,因此分配(map)的内存大小不受 JVM 的 -Xmx 参数限制,但是也是有大小限制的,不能超过Integer.MAX_VALUE.当超过字节限制时可能通过Position重新进行映射。
-
MappedByteBuffer 在处理大文件时性能的确很高,但也存内存占用、文件关闭不确定等问题,被其打开的文件只有在垃圾回收的才会被关闭,而且这个时间点是不确定的.
-
MappedByteBuffer 提供了文件映射内存的 mmap() 方法,也提供了释放映射内存的 unmap() 方法。然而 unmap() 是 FileChannelImpl 中的私有方法,无法直接显示调用。因此,用户程序需要通过 Java 反射的调用 sun.misc.Cleaner 类的 clean() 方法手动释放映射占用的内存区域。
class类下的getResource和ClassLoader类下的getResource方法的使用和区别:
-
MyClass.class.getResource(“xxx”)方法中传的参数如果是相对路径,那么传递的路径是相对于MyClass而言
-
MyClass.class.getResource(“/xxx”)方法中传的参数如果是绝对路径,那么传递的路径是相对于classpath而言
-
MyClass.class.getClassLoader().getResource(“xxx”)方法中传的参数如果是相对路径,那么传递的路径是相对于classpath而言
-
MyClass.class.getClassLoader().getResource(“/xxx”)方法中传的参数如果是绝对路径,也就是只要参数开头带/那么返回的都是null
java中URL对象和URI对象的区别:
-
URL对象是URI对象的子集。URL是一个具备特定语法的URI,用于表示互联网上的资源。URI是一个通用的标识符,用于表示任何类型的资源。
-
URL对象可以打开网络并读取数据,而URI对象表示一个URI字符串。
-
URL对象对于网络通信的细节做了更多的封装,而URI对象更加通用。URL对象包含了网络通信的协议、主机名、端口号、路径、查询参数等详细信息,封装了网络通信的细节。URI对象不包含网络通信的细节,只是一个标识符,更加通用。
-
URI对象可以表示相对路径,而URL对象需要一个基础URL。URI对象可以表示相对路径,即相对于当前URI的路径。URL对象表示的是一个完整的URL,如果需要表示相对路径,需要指定一个基础URL。
DirectByteBuffer
DirectByteBuffer是java NIO中的一种ByteBuffer类型,它是在堆外内存中直接分配的,意味着它可以避免在JVM堆内存和操作系统之间进行频繁的数据拷贝。
//DirectByteBuffer静态方法创建并分配内存,本质通过Unsafe.allocateMemory()底层
//调用malloc函数
public static ByteBuffer allocate(int capacity) {
if (capacity < 0)
throw createCapacityException(capacity);
return new HeapByteBuffer(capacity, capacity, null);
}
FileChannel
FileChannel是一个用于文化读写、映射和操作的通道,同时在并发环境它是线程安全的。FileChannel定义了transferFrom()和transferTo()两个抽象方法,**它通过在通道和通道之间建立连接实现数据传输。**
transferTo() 和 transferFrom() 方法的底层实现原理都是基于sendfile实现文件数据传输。
//标记当前系统内核是否支持sendfile
private static volatile boolean transfrSupported = true;
//标记当前系统内核是否支持文件描述符基于管道的sendfile调用
private static volatile boolean pipeSupported = true;
//用于标记当前系统内核是否支持文件描述符基于文件的sendfile调\=-
private static volatile boolean fileSupported = true;
标签:文件,JAVA,NIO,映射,URL,URI,内存,缓冲区,拷贝 From: https://blog.csdn.net/qqqwx_111116/article/details/144220654RocketMQ选择mmap+write这种零拷贝方式,适用于业务消息这种小块文件的数据持久化和传输。kafka采用sendfile这种零拷贝方式,使用系统日志消息这种高吞吐量的大块文件的数据持久化和传输。