当前位置: 首页 > 后端技术 > Java

告诉你什么是IO、NIO、BIO

时间:2023-04-01 15:42:05 Java

1、Stream和Channelstream不会自动缓冲数据,channel会使用系统提供的sendbuffer和receivebuffer(下层),stream只支持阻塞API,channel同时支持阻塞和非阻塞API。网络通道可以配合选择器实现复用。两者都是全双工的,即读和写可以同时进行。Stream虽然是单向流,但也是全双工的。2、IO模型同步:线程自己获取结果(一个线程)。例如:线程调用一个方法后,需要等待方法返回结果。异步:线程自己不获取结果,其他线程返回结果(至少两个线程)线程A调用一个方法后,继续向下运行,运行结果由线程B返回。调用channel后.read或stream.read一次,会从用户态切换到操作系统内核态,完成真正的数据读取,而read又分为两个阶段,分别是:等待数据阶段和拷贝数据阶段。根据UNIXNetworkProgramming-VolumeI,IO模型主要有以下几种。在此期间,用户线程被阻塞,无法进行其他操作。非阻塞IO用户线程一直循环调用read方法。如果内核空间没有数据可读,只有在等待阶段才立即返回。非阻塞用户线程找到内核空间。里面有数据后,等待内核空间执行拷贝数据,拷贝完成后返回结果。Java中通过Selector实现多路复用。没有事件时,一旦有一个或多个事件调用select方法就会被阻塞。发生后会处理相应的事件,从而实现多路复用和阻塞IO的区别。在阻塞IO模式下,如果线程因为accept事件被阻塞,read事件发生后,还需要等待accept事件完成在read事件的复用模式下,一个事件发生后,如果另一个事件发生blocked,不会影响事件的执行。异步IO线程1在调用方法后理解并返回,不会阻塞或者需要立即获取结果。当方法的结果出来后,线程2将结果返回给线程13。Zero-copy零拷贝是指不需要将数据复制到JVM内存中,它有以下三个优点Lessusermodeand内核状态切换不使用cpu计算,减少cpu缓存伪共享零拷贝适用于小文件传输传统IO问题传统IO将通过套接字写出文件Filef=newFile("helloword/data.txt");RandomAccessFilefile=newRandomAccessFile(file,"r");byte[]buf=newbyte[(int)f.length()];file.read(buf);Socketsocket=...;socket.getOutputStream().write(buf);内部工作流程如下Java本身没有IO读写能力,所以经过read方法被调用,要从Java程序的用户态切换到内核态,调用操作系统(Kernel)的read能力将数据读入内核缓冲区在此期间,用户线程被阻塞,操作系统使用DMA(DirectMemoryAccess)实现文件读取,在此期间不使用CPUDMA。从内核缓冲区读取到用户缓冲区(即byte[]buf)。在此期间,CPU会参与copy,不能使用DMA调用write方法。此时,将用户缓冲区(byte[]buf)中的数据写入socket缓冲区,CPU会参与复制,然后将数据写入网卡。Java没有这个能力,只好从用户态切换到内核态,调用操作系统的写能力,使用DMA将socketbuffer中的数据写入网卡。如果你不知道如何使用CPU,你可以看到有很多中间环节。Java的IO其实并不是物理设备层面的读写,而是缓存的一份拷贝。底层真正的读写是由操作系统完成的3次用户态和内核态的切换,这个操作是比较重量级的数据拷贝,通过DirectByteBufByteBuffer进行了4次NIO优化。一些步骤与优化前相同,除了一点:Java可以使用DirectByteBuffer将堆外内存映射到JVM内存,从而直接访问内存。使用这块内存不受JVM垃圾回收的影响,所以内存地址是固定的,有利于IO读写Java中的DirectByteBuf对象只维护了这块内存的虚引用。内存恢复分为两个步骤。DirectByteBuffer对象被垃圾收集,虚拟引用被添加到引用队列中。当引用对象ByteBuffer被垃圾回收时,虚拟引用对象Cleaner会被放入引用队列中,然后调用Cleaner的clean方法直接释放内存。DirectByteBufferrelease底层是Unsafe的freeMemory方法。通过专门的线程访问引用队列,根据虚引用释放堆外内存,减少一次数据拷贝,用户态和内核态的切换次数没有减少,进一步优化。1下面两种方式都是零拷贝,即不需要将数据拷贝到用户缓冲区(在JVM内存中)。底层采用linux2.1,提供提供的sendFile方法对应Java中的两个通道。调用transferTo/transferFrom方法复制数据。Java中调用transferTo方法后,需要从Java程序的用户态切换到内核态,使用DMA将数据读入内核缓冲区,不会使用CPU数据从内核传输过来buffer到socketbuffer,CPU会参与copy,最后使用DMA将socketbuffer中的数据写入网卡,不会占用CPU。该方法只发生一次用户态和内核态的数据切换。复制3次进一步优化2linux2.4再次优化了上面的方法。Java中调用transferTo方法后,需要从Java程序的用户态切换到内核态,使用DMA将数据读入内核缓冲区。一些偏移量和长度信息被复制到套接字缓冲区中,几乎没有消耗。DMA用于将内核缓冲区中的数据写入网卡,不使用CPU。整个过程只在用户态和内核态之间切换一次,数据拷贝了两次。4.AIOAIO用于解决数据复制阶段的阻塞问题。同步是指在进行读写操作时,线程需要等待结果,或者相当于空闲。异步是指在执行读写操作时,线程不必等待结果,但以后操作系统会通过回调从另一个线程获取结果。异步模型需要底层操作系统(Kernel)提供支持。Windows系统通过IOCP实现了真正的异步IOL。多路复用模拟异步IO,所以没有性能优势,是我坚持创作的动力。转载请注明出处!