关于nio:大话网络通信

1、术语并发 vs 并行并发和并行是相干的概念,但有一些小的区别。并发意味着两个或多个工作正在获得停顿,即便它们可能不会同时执行。例如,这能够通过工夫切片来实现,其中局部工作按程序执行,并与其余工作的局部混合。另一方面,当执行的工作能够真正同时进行时,就会呈现并行简略说启动一个线程在一个core上就是并行,启动两个线程在一个core上就是并发异步 vs 同步如果调用者在办法返回值或引发异样之前无奈获得停顿,则认为办法调用是同步的。另一方面,异步调用容许调用者在无限的步骤之后持续进行,并且能够通过一些附加机制 (它可能是已注册的回调、Future 或音讯)来告诉办法的实现简略来说Java API层来说的,如下 :ExecutorService executorService = Executors.newFixedThreadPool(2); Future<Boolean> future = executorService.submit(new Callable<Boolean>() { @Override public Boolean call() throws Exception { System.out.println("执行业务逻辑"); // 依据业务逻辑判断给定返回 return true; } }); future.get(); // 同步API,必须等到返回 if(future.isDone()) { future.get();// 异步API,只有执行完,再get后果 } 同步 API 能够应用阻塞来实现同步,但这不是必要的。CPU 密集型工作可能会产生相似 于阻塞的行为。一般来说,最好应用异步 API,因为它们保证系统可能进行非阻塞 vs 阻塞如果一个线程的提早能够无限期地提早其余一些线程,这就是咱们探讨的阻塞。一个很好的例子是,一个线程能够应用互斥来独占应用一个资源。如果一个线程无限期地占用资源(例如意外运行有限循环),则期待该资源的其余线程将无奈进行。相同,非阻塞意味着没有线程可能无限期地提早其余线程非阻塞操作优先于阻塞操作,因为当零碎蕴含阻塞操作时,零碎的总体进度并不能失去很好的保障 死锁 vs 饥饿 vs 活锁当多个线程在期待对方达到某个特定的状态以便可能获得停顿时,就会呈现死锁。因为没有其余线程达到某种状态,所有受影响的子系统都无奈持续运行。死锁与阻塞密切相关,因为线程可能无限期地提早其余线程的过程在死锁的状况下,没有线程能够获得停顿,相同,当有线程能够获得停顿,但可能有一个或多个线程不能获得停顿时,就会产生饥饿。典型的场景是一个调度算法,它总是抉择高优先级的工作而不是低优先级的工作。如果传入的高优先级工作的数量始终足够多,那么低优先级工作将永远不会实现活锁相似于死锁,因为没有线程获得停顿。不同之处在于,线程不会被解冻在期待别人停顿的状态中,而是一直地扭转本人的状态。一个示例场景是,两个线程有两个雷同资源可用时。他们每一个都试图取得资源,但他们也会查看对方是否也须要资源。 如果资源是由另一个线程申请的,他们会尝试获取该资源的另一个实例。在可怜的情 况下,两个线程可能会在两种资源之间“反弹”,从不获取资源,但总是屈服于另一种资源2、BIO vs NIOBIOserverSocket.accept(),这里会阻塞socket.getInputStream.read(),也会阻塞 尽管能够应用了线程池,因为read()办法的阻塞,其实线程池也是不能复用的,说白了,就是须要一个客户端一个线程进行服务 思考:那BIO就没有应用场景了吗?其实不是,BIO在建设长连贯的流式传输场景还是很有用的,比如说HDSF,客户端向DataNode传输数据应用的就是建设一个BIO的管道,流式上传数据的。此时引入一个问题,那HDFS DataNode就不思考到线程阻塞么?是这样的,其实要晓得你不可能多个客户端上传文件都是针对某个DataNode(NameNode会进行抉择DataNode),所以线程阻塞的压力是会摊派的。NIO还是善于小数据量的RPC申请,能承受百万客户端的连贯 NIONIO中有三个重要组件 : Buffer(ByteBuffer次要应用)、Channel(双向通道,可读可写)和Selector(多路复用选择器) Buffer罕用的就是 ByteBuffer,缓冲池,能够作为channel写的单位,也能够承受channel读取的返回外面重要的属性 :position、capacity、flip、limit和hasRemain 每个channel都须要记录可能切分的音讯,因为ByteBuffer不能被多个channel应用,因而须要为每个channel保护一个独立的ByteBuffer。ByteBuffer不能太大,比方一个ByteBuffer 1M的话,须要反对百万连贯要1TB内存,因而须要设计大小可变的ByteBuffer 1、首先调配一个较小的buffer,比方4k,如果发现不够的话,再调配8kb的buffer,将4kb buffer内容拷贝到8kb buffer,有点是音讯间断容易解决,毛病是数据拷贝消耗性能 2、多个数组组成buffer,一个数组不够,把多进去的内容写入新的数组,毛病不间断解析简单,有点防止了拷贝引起的性能损耗 ...

March 29, 2023 · 1 min · jiezi

关于nio:分布式-令人头疼的堆外内存泄露怎么排查

作者:鲍凤其 爱可生 dble 团队开发成员,次要负责 dble 需要开发,故障排查和社区问题解答。少说废话,放码过去。 本文起源:原创投稿 *爱可生开源社区出品,原创内容未经受权不得随便应用,转载请分割小编并注明起源。 大家在应用 Java NIO 的过程中,是不是也遇到过堆外内存泄露的问题?是不是也苦恼过如何排查? 上面就给大家介绍一个在dble中排查堆外内存泄露的案例。 景象有客户在应用dble之后,有一天dble对后端MySQL实例的心跳检测全副超时,导致业务中断,最初通过重启解决。 剖析过程dble 日志首先当然是剖析dble日志。从dble日志中能够发现: 故障工夫点所有后端 MySQL 实例心跳都超时日志中呈现大量“You may need to turn up page size. The maximum size of the DirectByteBufferPool that can be allocated at one time is 2097152, and the size that you would like to allocate is 4194304”的日志日志片段: //心跳超时2022-08-15 11:40:32.147 WARN [TimerScheduler-0] (com.actiontech.dble.backend.heartbeat.MySQLHeartbeat.setTimeout(MySQLHeartbeat.java:251)) - heartbeat to [xxxx:3306] setTimeout, previous status is 1 // 堆外内存可能泄露的可疑日志2022-08-15 11:40:32.153 WARN [$_NIO_REACTOR_BACKEND-20-RW] (com.actiontech.dble.buffer.DirectByteBufferPool.allocate(DirectByteBufferPool.java:76)) - You may need to turn up page size. The maximum size of the DirectByteBufferPool that can be allocated at one time is 2097152, and the size that you would like to allocate is 4194304通过下面的日志猜想: ...

December 15, 2022 · 3 min · jiezi

关于nio:NIO源码JavaNIO源码-JNI分析二Java-NIO源码分析

没看过的倡议先看上一篇,原本打算讲讲linux内核,也看了一些书籍,可是c放了太久了,看代码切实头疼,就先放弃了,写写业务也没必要卷这么深吧。就讲到调用底层api为止我感觉刚刚好。不太善于将源码联合讲故事,所以整片略显干燥,将就看下吧~~demopublic class ServerConnect{ public static void main(String[] args) { selector(); } public static void handleAccept(SelectionKey key) throws IOException{ ServerSocketChannel ssChannel = (ServerSocketChannel)key.channel(); SocketChannel sc = ssChannel.accept(); sc.configureBlocking(false); sc.register(key.selector(), SelectionKey.OP_READ,ByteBuffer.allocateDirect(1024)); } public static void handleRead(SelectionKey key) throws IOException{ SocketChannel sc = (SocketChannel)key.channel(); ByteBuffer buf = (ByteBuffer)key.attachment(); long bytesRead = sc.read(buf); while(bytesRead>0){ buf.flip(); while(buf.hasRemaining()){ System.out.print((char)buf.get()); } buf.clear(); bytesRead = sc.read(buf); } if(bytesRead == -1){ sc.close(); } } public static void handleWrite(SelectionKey key) throws IOException{ ByteBuffer buf = (ByteBuffer)key.attachment(); buf.flip(); SocketChannel sc = (SocketChannel) key.channel(); while(buf.hasRemaining()){ sc.write(buf); } buf.compact(); } public static void selector() { Selector selector = null; ServerSocketChannel ssc = null; try{ selector = Selector.open(); ssc= ServerSocketChannel.open(); ssc.socket().bind(new InetSocketAddress(8080)); ssc.configureBlocking(false); ssc.register(selector, SelectionKey.OP_ACCEPT); while(true){ if(selector.select(3000) == 0){ continue; } Iterator<SelectionKey> iter = selector.selectedKeys().iterator(); while(iter.hasNext()){ SelectionKey key = iter.next(); if(key.isAcceptable()){ handleAccept(key); } if(key.isReadable()){ handleRead(key); } if(key.isWritable() && key.isValid()){ handleWrite(key); } if(key.isConnectable()){ System.out.println("isConnectable = true"); } iter.remove(); } } }catch(IOException e){ e.printStackTrace(); }finally{ try{ if(selector!=null){ selector.close(); } if(ssc!=null){ ssc.close(); } }catch(IOException e){ e.printStackTrace(); } } }}源码剖析Selector.open()此处会有SelectorProvider是个模板办法,不必零碎的实现不同,这就很蛋疼,没有EPollSelectorProvider,莫的方法,只能去github找了一份源码迁徙到gitee,jdk8感兴趣的能够自行下载,想看其余版本的同学就要自行寻找了。 ...

September 23, 2022 · 9 min · jiezi

关于nio:Netty网络编程NIO与零拷贝

1.什么是DMA2.什么是用户态和内核态3.一般BIO的拷贝流程剖析4.mmap零碎函数5.sendFile零碎函数(零拷贝)6.java堆外内存如何回收 1.什么是DMA DMA(Direct Memory Access间接存储器拜访),咱们先从一张图来理解一下DMA是一个什么安装。 假如在什么没有DMA的状况下,如果CPU想从内存里读取数据并发送到网卡中,在读的过程中,咱们能够晓得:1.1)CPU的速度最快。1.2)当CPU在内存中读取数据的时候,读取的速度瓶颈在于内存的读写速度。1.3)当CPU实现读取,将数据写入网卡的时候,写入的速度瓶颈在于网卡的速度。1.4)CPU在读写的时候,是无奈做其它事件的。 这个时候咱们就能够得出结论: 1.5)cpu的速度取决于这一系列操作环上最慢的那一个。1.6)cpu利用率极低,大部分工夫都在期待IO。 此时如果有了DMA,那么咱们的读写就会变得和如图一样: CPU只须要把读写工作委托给DMA,帮助CPU搬运数据,这些操作都由DMA主动执行,而不须要依赖于CPU的大量中断负载,此时cpu就能够去做其它的事件了。 2.什么是用户态和内核态其实最初咱们的服务器程序,都是要在linux上运行的,Linux依据命令的重要水平,也分为不同的权限。Linux操作系统就将权限分成了2个等级,别离就是用户态和内核态。 用户态:用户态的过程可能拜访的资源就有极大的限度,有些指令操作无关痛痒,随便执行都没事,大部分都是属于用户态的。 内核态:运行在内核态的过程能够“随心所欲”,能够间接调用操作系统内核函数。 比方:咱们调用malloc函数申请动态内存,此时cpu就要从用户态切换到内核态,调用操作系统底层函数来申请空间。 3.一般BIO的拷贝流程剖析 咱们来看一下一般IO的拷贝流程: 咱们来看这一段代码: 咱们先从服务器上读取了一个文件,而后通过连贯和流传输到申请客户端上,咱们能够看到大抵的申请流程是这样的: 当程序或者操作者对CPU收回指令,这些指令和数据暂存在内存里,在CPU闲暇时传送给CPU,CPU解决后把后果输入到输出设备上 3.1)用户态程序接到申请,要从磁盘上读取文件,切换到内核态,这里是第1次用户态内核态切换。3.2)当要读取的文件通过DMA复制到内核缓冲区的时候,咱们还要把这些数据传送给CPU,CPU之后再把这些数据送到输出设备上,这里是第1次cpu拷贝。3.3)当内核态程序数据读取结束,切换回用户态,这里是第2次内核态用户态切换。3.4)当程序创立一个缓冲区,并将数据写入socket缓冲区,这里是第3次用户态内核态切换。3.5)此时cpu要把数据拷贝到socket缓冲区,这里是第2次cpu拷贝。3.6)实现所有操作之后,应用程序从内核态切换回用户态,继续执行后续操作(程序到此为止)。这里是第4次用户态内核态切换。 此时咱们能够看出,传统的IO拷贝流程,经验了4次用户态和内核态的切换,进行了2次cpu复制,性能消耗微小,咱们有没有更节俭资源的做法呢? 4.mmap零碎函数 linux的底层内核函数mmap函数对底层进行了一个优化: 4.1)用户态程序接到申请,要从磁盘上读取文件,切换到内核态,这里是第1次用户态内核态切换。4.2)当要读取的文件通过DMA复制到内核缓冲区实现,此时内核缓冲区,用户数据缓冲区共享一块物理内存空间,这里就无需cpu拷贝到用户空间中。4.3)此时读取文件结束,用户切换回用户态,这是第2次用户态内核态切换。4.4)申请一块缓冲区,须要调用内核函数,这是第3次用户态内核态切换。4.5)内核态通过cpu复制,将共享空间的数据内容拷贝到socket缓冲区中,这是第1次cpu拷贝。4.6)实现所有操作之后,应用程序从内核态切换回用户态,继续执行后续操作(程序到此为止)。这里是第4次用户态内核态切换。 咱们能够看出,mmap函数少了一次cpu复制,对于空间的利用率进步了,不过还是须要4次用户态和内核态的切换。 5.sendFile零碎函数(零拷贝) 零拷贝:指的是没有cpu拷贝,数据还是须要通过DMA拷贝到内存中,再发送进来的。 4.1)用户态程序接到申请,要从磁盘上读取文件,切换到内核态,这里是第1次用户态内核态切换。4.2)当数据通过DMA复制进入内核缓冲区并且实现,咱们还是通过cpu复制把数据复制到socket缓冲区,不过这里的cpu复制只复制很大量的内容,能够简直忽略不计。 4.3)此时数据通过DMA复制发送给目的地。4.4)程序切换回用户态,这是第2次用户态内核态切换。 咱们发现,sendFile零碎函数,只须要两次用户态到内核态的切换,而且一次cpu复制都不须要,大大节约了资源。 6.java堆外内存如何回收 介绍了零拷贝技术,其实Netty底层是应用堆外内存来实现零拷贝技术的,api:ByteBuffer.allocateDirect(),这条命令间接在堆外内存开拓了一块空间,咱们都晓得GC是收集堆内存垃圾的,那堆外内存又是如何收集的呢? 堆外内存的劣势:堆外内存的劣势在于IO上,java在应用socket发送数据的时候,如果应用堆外内存,就能够间接应用堆外内存往socket上发送数据,就节俭了先把堆外数据拷贝到堆内数据的开销。 咱们先来看看ByteBuffer.allocateDirect()的源码:咱们能够看出,java应用unsafe类来调配了一块堆外内存 那么堆外内存是如何回收的呢?咱们来看这样一行代码: cleaner就是用来回收堆外内存的,然而它是如何工作的呢?咱们认真钻研一下cleaner这个类,它是一个链表构造: 通过create(Object,Runnable)办法创立cleaner对象,调用本身的add办法,将其退出链表中。 clean有个重要的clean办法, 它首先将对象从本身链表中删除:而后执行this.thunk的run办法,thunk就是由创立的时候传入的Runnable函数:能够看出,run办法是一个开释堆外内存的函数。 逻辑咱们曾经梳理完,然而JVM如何开释其占用的堆外内存呢?如何跟Cleaner关联起来? 首先,Cleaner继承了PhantomReference(虚援用),对于强脆弱虚援用,在后面的博客曾经赘述过:深刻了解JVM(八)——强脆弱虚援用 简略地再介绍一下虚援用,当GC某个对象的时候,如果此对象上有虚援用,会将其退出PhantomReference退出到ReferenceQueue队列。 Cleaner继承PhantomReference,而PhantomReference又继承Reference,Reference初始化的时候,会运行一个动态代码块: 咱们能够看出,ReferenceHandler作为一个优先级比拟高的守护线程被启动了。 在看他的解决逻辑之前,咱们先理解一下对象的四种状态; Active:激活。创立ref对象时就是激活状态Pending:期待入援用队列。所对应的援用被GC,就要入队。Enqueued:入队状态。 如果指定了refQueue生产pending挪动到enqueued状态。refQueue.poll时进入生效状态如果没有指定refQueue,间接到生效状态。Inactive:生效接下来咱们能够看业务逻辑了: 这是一个死循环,咱们再往里点: static boolean tryHandlePending(boolean waitForNotify) { Reference<Object> r; Cleaner c; try { //可能有多线程对一个援用队列操作,所以要加锁 synchronized (lock) { //如果以后对象是 期待入援用队列 的状态 if (pending != null) { r = pending; // 'instanceof' might throw OutOfMemoryError sometimes // so do this before un-linking 'r' from the 'pending' chain... //转化为clean对象 c = r instanceof Cleaner ? (Cleaner) r : null; // unlink 'r' from 'pending' chain //解除援用 pending = r.discovered; r.discovered = null; } else { //如果没有,期待唤醒 // The waiting on the lock may cause an OutOfMemoryError // because it may try to allocate exception objects. if (waitForNotify) { lock.wait(); } // retry if waited return waitForNotify; } } } catch (OutOfMemoryError x) { // Give other threads CPU time so they hopefully drop some live references // and GC reclaims some space. // Also prevent CPU intensive spinning in case 'r instanceof Cleaner' above // persistently throws OOME for some time... Thread.yield(); // retry return true; } catch (InterruptedException x) { // retry return true; } // Fast path for cleaners //革除内存 if (c != null) { c.clean(); return true; } ReferenceQueue<? super Object> q = r.queue; if (q != ReferenceQueue.NULL) q.enqueue(r); return true; }咱们能够得出:1)当对象状态是Pending的时候,就会进入if,将这个对象转化为clean对象,并将这个援用置空2)进行clean的垃圾收集3)这个线程始终在后盾启动,如果有援用,就会唤醒该线程。 ...

July 22, 2022 · 2 min · jiezi

关于nio:线程安全

本文次要记录最近在解决一个兄弟的代码bug中的几点比较突出的问题. 多线程注意事项1、HashMap 不是线程平安的 在应用多线程的时候 , 肯定要留神本人所应用和设计的数据结构是否是线程平安的.比方 Java中平时用的最多的Map汇合就是HashMap了,它是线程不平安的为了避免出现线程平安的问题,不能应用HashMap作为成员变量,要寻求应用线程平安的Map 如 HashTable 、SynchronizedMap、ConcurrentHashMap ; 当然,也能够本人写锁来管制hashMap的平安. eg: 读写锁如下,是咱们现成的我的项目中的代码,多线程的参数传递,应用了HashMap,导致了很大的生产事变 2、多线程在进行对象的拷贝时,务必应用深拷贝模式 存在对象时 , 倡议应用序列化对象的形式进行深拷贝 . 因为对象的数据结构较为简单 , hashMap的putAll() 甚至是apache开源的CloneUtils.clone() 都不可能完全正确的进行深拷贝.2.1 此处,插播一个问题,不晓得大家留神到没有,下面的代码中,HashMap 在进行初始化的时候,并没有指定map的大小,这里也就出了一个新的问题: 服务始终在占用高CPU HashMap 在并发的环境下进行rehash的时候会造成链表的闭环,因而在进行get()操作的时候导致了CPU占用100% . 为什么进行rehash ? 具体去看源码 , 大略就是map的大小触碰到了HashMap的阈值threshold(map实现的时候就有了),当HashMap的容量达到 threshold 时就须要进行扩容,这个时候就要进行ReHash操作了,能够看addEntry函数的实现,当size达到threshold时会调用 resize 进行扩容2.2 在排查CPU 占用的问题的时候,咱们定位到一个jdk的bug : Selector BUG上面咱们对这个进行一个解说 2.2.1 Selector BUG呈现的起因 Selector的轮询后果为空,也没有wakeup或新音讯解决,则产生空轮询,CPU使用率100%,(因为selector的select办法,返回numKeys是0,所以上面本应该对key值进行遍历的事件处理基本执行不了,又回到最下面的while(true)循环,周而复始,一直的轮询,直到linux零碎呈现100%的CPU状况,其它执行工作干不了活) 2.2.2 Netty的解决办法 对Selector的select操作周期进行统计,每实现一次空的select操作进行一次计数 若在某个周期内间断产生N(默认是512)次空轮询,则触发了epoll死循环bug 重建Selector,判断是否是其余线程发动的重建申请,若不是则将原SocketChannel从旧的Selector上去除注册,从新注册到新的Selector上,并将原来的Selector敞开。 2.2.3 该bug相干信息 https://bugs.java.com/bugdatabase/view_bug.do?bug_id=2147719 https://bugs.java.com/bugdatabase/view_bug.do?bug_id=6403933 这个bug的形容内容为,在NIO的selector中,即便是关注的select轮询事件的key为0的话,NIO照样一直的从select本应该阻塞的状况中wake up出.3、发现Cpu 打满 常用命令(三板斧) 1、top命令,按P依照CPU使用率排序找到占用率最高的过程pid2、top -Hp pid 命令找到占用率最高的线程tid3、printf "%x\n" tid 命令把十进制线程tid转化为十六进制4、jstack -l tid > stack.txt 打印栈信息5、用十六进制的tid在堆栈信息中搜寻6、如果没有搜寻到,反复执行几遍第4步,肯定能有本文参考: https://pdai.tech/md/java/io/... ...

April 20, 2022 · 1 min · jiezi

关于nio:Java里的零拷贝

对于linux零拷贝技术能够先看下后面一篇文章IO零拷贝,因为java里的零拷贝底层也是依赖的操作系统实现,须要阐明下,Linux提供的零拷贝技术Java并不是全反对,只反对2种:mmap内存映射、sendfile,别离是由FileChannel.map()与FileChannel.transferTo()/transferFrom()实现。波及的类次要有FileChannel,MappedByteBuffer,DirectByteBuffer。 MappedByteBuffer先看下ChannelFile的map办法: public abstract MappedByteBuffer map(MapMode mode, long position, long size)throws IOException;mode 限定内存映射区域(MappedByteBuffer)对内存映像文件的拜访模式,有只读,读写与写时拷贝三种。position 文件映射的起始地址,对应内存映射区域的首地址size 文件映射的字节长度,从position往后的字节数,对应内存映射区域的大小map办法正是NIO基于内存映射(mmap)这种零拷贝形式的一种实现形式。办法返回一个MappedByteBuffer,MappedByteBuffer继承于ByteBuffer,扩大的办法有force(),load(),isLoad()这三个办法: force(),对于处于READ_WRITE模式下的缓冲区,将对缓冲区内容共性强制刷新到本地文件load(),将缓冲区的内容载入物理内存中,并返回这个缓冲区的援用isLoad(),判断缓冲区的内容是否在物理内存中,是返回true,不是返回false看个示例 public class MappedByteBufferDemo { public static final String CONTENT = "zero copy by MappedByteBuffer"; public static final String FILE_NAME= "zero_copy/mmap.txt"; public static final String CHARSET = "UTF-8"; /** * 写文件数据:关上文件通道 fileChannel 并提供读权限、写权限和数据清空权限, * 通过 fileChannel 映射到一个可写的内存缓冲区 mappedByteBuffer, * 将指标数据写入 mappedByteBuffer,通过 force() 办法把缓冲区更改的内容强制写入本地文件。 */ @Test public void writeToFileByMappedByteBuffer(){ //文件门路依据理论来定,我是放在我的项目的resources目录下 Path path = Paths.get(getClass().getResource("/"+FILE_NAME).getPath()); byte[] bytes = CONTENT.getBytes(Charset.forName(CHARSET)); try(FileChannel fileChannel = FileChannel.open(path, StandardOpenOption.READ, StandardOpenOption.WRITE,StandardOpenOption.TRUNCATE_EXISTING)){ MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, bytes.length); if (mappedByteBuffer != null){ mappedByteBuffer.put(bytes); mappedByteBuffer.force(); } }catch (IOException e){ e.printStackTrace(); } } /** * * 读文件数据:关上文件通道 fileChannel 并提供只读权限,通过 fileChannel 映射到一个 * 只可读的内存缓冲区 mappedByteBuffer,读取 mappedByteBuffer 中的字节数组即可失去文件数据。 */ @Test public void readFileFromMappedByteBuffer(){ Path path = Paths.get(getClass().getResource("/"+FILE_NAME).getPath()); int length = CONTENT.getBytes(Charset.forName(CHARSET)).length; try(FileChannel fileChannel = FileChannel.open(path,StandardOpenOption.READ)){ MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, length); if (mappedByteBuffer != null){ byte[] bytes = new byte[length]; mappedByteBuffer.get(bytes); String content = new String(bytes, StandardCharsets.UTF_8); assertEquals(content,"zero copy by MappedByteBuffer"); } }catch (IOException e){ e.printStackTrace(); } }}这里咱们再来看看map()办法,它是在FileChannelImpl类里实现的,来看下外围代码: ...

January 11, 2022 · 5 min · jiezi

关于nio:IO零拷贝

用户态与内核态Linux操作系统体系架构分为用户态与内核态。内核次要管制计算机的硬件资源,为下层利用提供运行反对。用户态为下层利用的流动空间,应用程序的执行须要内核的反对,如CPU资源,存储资源,IO资源等,用户态通过内核提供的拜访接口也就是零碎调用来应用这些资源。 基于间接外在(DMA)实现的文件传输 应用程序调用read(),上下文切换到内核,DMA将磁盘数据复制到内核的缓存空间read()返回,上下文切换到用户态,CPU将数据复制到用户的缓存空间应用程序调用write(),上下文再次切换到内核,CPU将数据复制到内核socket缓存write()返回,上下文再次切换到用户态,DMA将socket缓存数据复制到网卡缓存上这里一共呈现了4次上下文切换,4次数据拷贝通过sendfile实现的零拷贝 应用程序发现sendfile零碎调用,用户空间切换到内核态通过DMA将磁盘文件拷贝到内核缓冲区DMA收回中断,CPU解决中断,将数据从内核缓冲区拷贝到内核中与socket相干的缓冲区。而后sendfile零碎调用返回,从内核态切换到用户态DMA将内核空间socket缓冲区数据拷贝到网卡这里一共呈现了2次高低切换,3次数据拷贝未完。。。

January 6, 2022 · 1 min · jiezi

关于nio:RPC的通信NettyNetty的底层是NioJava的Io模型你了解多少

RPC的通信Netty,Netty的底层是Nio,Java的Io模型你理解多少? I/O 模型简略的了解:就是用什么样的通道进行数据的发送和接管,很大水平上决定了程序通信的性能,Java 共反对 3 种网络编程模型/IO 模式:BIO、NIO、AIO。 什么是BIO?同步并阻塞(传统阻塞型),服务器实现模式为一个连贯一个线程,即客户端有连贯申请时服务器 端就须要启动一个线程进行解决,如果这个连贯不做任何事件会造成不必要的线程开销、BIO 形式实用于连贯数目比拟小且固定的架构,这种形式对服务器资源要求比拟高,并发局限于利用中,JDK1.4以前的惟一抉择,但程序简略易了解。 同步非阻塞IO同步非阻塞,服务器实现模式为一个线程解决多个申请(连贯),即客户端发送的连贯申请都会注 册到多路复用器上,多路复用器轮询到连贯有 I/O 申请就进行解决,NIO 形式实用于连贯数目多且连贯比拟短(轻操作)的架构,比方聊天服务器,弹幕零碎,服务器间通信等。编程比较复杂,JDK1.4 开始反对。 Java AIO(NIO.2) : 异步非阻塞,AIO 引入异步通道的概念,采纳了 Proactor 模式,简化了程序编写,无效 的申请才启动线程,它的特点是先由操作系统实现后才告诉服务端程序启动线程去解决,个别实用于连接数较 多且连接时间较长的利用。AIO 形式应用于连贯数目多且连贯比拟长(重操作)的架构,比方相册服务器,充沛调用 OS 参加并发操作,编程比较复杂,JDK7 开始反对。Java BIO 问题剖析每个申请都须要创立独立的线程,与对应的客户端进行数据 Read,业务解决,数据 Write 。当并发数较大时,须要创立大量线程来解决连贯,系统资源占用较大。连贯建设后,如果以后线程临时没有数据可读,则线程就阻塞在 Read 操作上,造成线程资源节约nio介绍Java NIO 全称 java non-blocking IO,是指 JDK 提供的新 API。从 JDK1.4 开始,Java 提供了一系列改良的输出/输入的新个性,被统称为 NIO(即 New IO),是同步非阻塞的NIO 相干类都被放在 java.nio 包及子包下,并且对原 java.io 包中的很多类进行改写。【根本案例】NIO 有三大外围局部:Channel(通道),**Buffer(缓冲区), Selector(**选择器)Selector 、 Channel 和 Buffer 的关系图 Selector 、 Channel 和 Buffer 的关系图 ...

August 17, 2021 · 1 min · jiezi

关于nio:linuxbio与nio的区别

BIO 一个线程对应一个连贯,read时阻塞,直到有数据返回. NIOselectapplication晓得有I/O工夫产生,但并不知道哪几个流,所以只能轮询所有流读取数据,工夫复杂度O(n),同时解决的流越多,工夫越多,且有最大连贯限度 poll application将文件描述符数组拷贝到内核空间,内核轮询每个fd对应的设施状态,工夫复杂度O(n),没有最大连接数的限度,基于链表来存储文件描述符fd epoll事件驱动,epoll会把哪个流产生了怎么的I/O事件告诉咱们,首先通过create函数创立一个epoll实例,而后应用ctl add/delete函数新增删除epoll实例上要监听的事件类型,调用wait函数期待事件产生,当产生事件时,内核会告诉application处理事件,application再去读取流epoll应用mmap文件映射,能够实现application和内核空间的音讯零拷贝,缩小复制开销. select,poll,epoll之间的区别过程最大连接数:select:有,受linux最大连接数影响poll:无,基于链表来存储epoll:有链接限度,但数量很大 1G内存机器能够关上10万连贯,fd剧增的IO效率select:线性遍历,不论链接是否沉闷均会遍历poll:同selectepoll:基于事件回调,沉闷socket才会被动调起callback,沉闷链接少时,性能很好,但当所有连贯都沉闷时,有性能问题消息传递形式select: 内核与用户空间之间须要互相拷贝poll:同上epoll:通过mmap共享内存

July 5, 2021 · 1 min · jiezi

关于nio:Java-NIO-基础四-选择器

从最根底的层面上来看,选择器提供了问询通道是否就绪操作I/O的能力,选择器能够监控注册在下面的多个通道,通道注册时会返回选择键(记录通道与选择器之间的关联关系),选择器管理者这些注册的键、和就绪状态键的汇合 SelectableChannel所有继承SelectableChannel的通道都能够在选择器中注册,FileChannel没有继承这个类,所以无奈应用选择器 选择键(SelectionKey) 选择键是选择器的重点内容,选择器就绪的通道通过返回选择键汇合来告诉 public abstract class SelectionKey { public static final int OP_READ public static final int OP_WRITE public static final int OP_CONNECT public static final int OP_ACCEPT public abstract SelectableChannel channel(); public abstract Selector selector(); public abstract void cancel(); public abstract boolean isValid(); public abstract int interestOps(); public abstract void interestOps(int ops); public abstract int readyOps(); public final boolean isReadable() public final boolean isWritable() public final boolean isConnectable() public final boolean isAcceptable() public final Object attach(Object ob) public final Object attachment()}选择键保护了通道和选择器之间的关联,能够通过选择键获取Channel或Selector,键对象示意一种非凡的关联关系,当这种关系须要终止时,能够调用cancel()办法勾销,调用这个办法时,不会立刻被勾销,而是将这个键放到被勾销的汇合里,当Selector下次调用select()办法时会真正被清理掉。当通道敞开时,选择键会主动被勾销,当选择器敞开时,所有键都会被清理掉。 ...

November 19, 2020 · 3 min · jiezi

关于nio:NIO优化原理和Tomcat线程模型

1、I/O阻塞书上说BIO、NIO等都属于I/O模型,然而I/O模型这个范畴有点含混,我为此走了不少弯路。咱们日常开发过程中波及到NIO模型利用,如Tomcat、Netty中等线程模型,能够间接将其视为网络I/O模型。本文还是在根底篇章中介绍几种I/O模型形式,前面就默认只解说网络I/O模型了。 1.1、I/O分类BIO、NIO、AIO等都属于I/O模型,所以它们优化的都是零碎I/O的性能,因而首先,咱们要分明常见的I/O有哪些分类: I/O品种场景java中到利用内存I/O从内存中读取数据,将数据写入内存线程从内存中将数据读取到工作空间,将值在工作空间实现更改后,将值由工作空间刷新到内存中(jmm)磁盘I/O读取磁盘文件,写文件到磁盘线程从内存中将数据读取到工作空间,将值在工作空间实现更改后,将值由工作空间刷新到内存中(jmm)网络I/O网络数据的读写和传输tcp/udp的形象api即socket 通信 (java.net)1.2、I/O过程和性能I/O(Input/Output)即数据的输出/输入,为什么大家很关怀I/O的性能呢?因为I/O存在的范畴很广,在高并发的场景下,这部分性能会被有限放大。而且与业务无关,是能够有对立解决方案的。 所有的零碎I/O都分为两个阶段:期待就绪和数据操作。举例来说,读函数,分为期待零碎可读和真正的读;同理,写函数分为期待网卡能够写和真正的写: 期待就绪:期待数据就绪,个别是将数据加载到内核缓存区。无论是从磁盘、网络读取数据,程序能解决的都是进入内核态之后的数据,在这之前,cpu会阻塞住,期待数据进入内核态。数据操作:数据就绪后,个别是将内核缓存中的数据加载到用户缓存区。须要阐明的是期待就绪的阻塞是不应用CPU的,是在“空等”;而真正的读写操作的阻塞是应用CPU的,真正在”干活”,而且这个过程十分快,属于memory copy,带宽通常在1GB/s级别以上,能够了解为根本不耗时。这就呈现一个奇怪的景象 -- 不应用CPU的“期待就绪”,却比理论应用CPU的“数据操作”,占用CPU工夫更多。 传统阻塞I/O模型,即在读写数据过程中会产生阻塞景象。当用户线程收回I/O申请之后,内核会去查看数据是否就绪,如果没有就绪就会期待数据就绪,而用户线程就会处于阻塞状态,用户线程交出CPU。当数据就绪之后,内核会将数据拷贝到用户线程,并返回后果给用户线程,用户线程才会解除block状态。 明确的是,让当前工作线程阻塞,期待数据就绪,是很节约线程资源的事件,上述三种I/O都有肯定的优化计划: 磁盘I/O:古代电脑中都有一个DMA(Direct Memory Access 间接内存拜访) 的外设组件,能够将I/O数据间接传送到主存储器中并且传输不须要CPU的参加,以此将CPU解放出来去实现其余的事件。网络I/O:NIO、AIO等I/O模型,通过向事件选择器注册I/O事件,基于就绪的事件来驱动执行I/O操作,防止的期待过程。内存I/O:内存局部没波及到太多阻塞,优化点在于缩小用户态和内核态之间的数据拷贝。nio中的零拷贝就有mmap和sendfile等实现计划。1.3、网络I/O阻塞这里认真的讲讲网络I/O模型中的阻塞,即socket的阻塞。在计算机通信畛域,socket 被翻译为“套接字”,它是计算机之间进行通信的一种约定或一种形式,是在tcp/ip协定上,形象进去的一层网络通讯协定。 同下面I/O的过程一样,网络I/O也同样分成两个局部: 期待网络数据达到网卡,读取到内核缓冲区。从内核缓冲区复制数据到用户态空间。每个 socket 被创立后,都会调配两个缓冲区,输出缓冲区和输入缓冲区: 输出缓冲区:当应用 read()/recv() 读取数据时,(1)首先会查看缓冲区,如果缓冲区中有数据,那么就读取,否则函数会被阻塞,直到网络上有数据到来。(2)如果要读取的数据长度小于缓冲区中的数据长度,那么就不能一次性将缓冲区中的所有数据读出,残余数据将一直积压,直到有 read()/recv() 函数再次读取。(3)直到读取到数据后 read()/recv() 函数才会返回,否则就始终被阻塞。输入缓冲区:当应用 write()/send() 发送数据时,(1)首先会查看缓冲区,如果缓冲区的可用空间长度小于要发送的数据,那么 write()/send() 会被阻塞(暂停执行),直到缓冲区中的数据被发送到指标机器,腾出足够的空间,才唤醒 write()/send() 函数持续写入数据。(2) 如果TCP协定正在向网络发送数据,那么输入缓冲区会被锁定,不容许写入,write()/send() 也会被阻塞,直到数据发送结束缓冲区解锁,write()/send() 才会被唤醒。(3)如果要写入的数据大于缓冲区的最大长度,那么将分批写入。(4)直到所有数据被写入缓冲区 write()/send() 能力返回。由此可见在网络I/O中,会有很多的因素导致数据的读取和写入过程呈现阻塞,创立socket连贯也一样。socket.accept()、socket.read()、socket.write()这类函数都是同步阻塞的,当一个连贯在解决I/O的时候,零碎是阻塞的,该线程以后的cpu工夫片就节约了。 2、阻塞优化2.1、BIO、NIO、AIOBIO、NIO、AIO比照以socket.read()为例子: 传统的BIO外面socket.read(),如果TCP RecvBuffer里没有数据,函数会始终阻塞,直到收到数据,返回读到的数据。对于NIO,如果TCP RecvBuffer有数据,就把数据从网卡读到内存,并且返回给用户;反之则间接返回0,永远不会阻塞。最新的AIO(Async I/O)外面会更进一步:岂但期待就绪是非阻塞的,就连数据从网卡到内存的过程也是异步的。换句话说,BIO里用户最关怀“我要读”,NIO里用户最关怀”我能够读了”,在AIO模型里用户更须要关注的是“读完了”。 NIONIO的优化体现在两个方面: 网络I/O模式的优化,通过非阻塞的模式,进步了CPU的使用性能。内存I/O的优化,零拷贝等形式,让数据在内核态和用户态之前的传输耗费升高了。NIO一个重要的特点是:socket次要的读、写、注册和接管函数,在期待就绪阶段都是非阻塞的,真正的I/O操作是同步阻塞的(耗费CPU但性能十分高)。 NIO的次要事件有几个:读就绪、写就绪、有新连贯到来。 咱们首先须要注册当这几个事件到来的时候所对应的处理器。而后在适合的机会通知事件选择器:我对这个事件感兴趣。对于写操作,就是写不进来的时候对写事件感兴趣;对于读操作,就是实现连贯和零碎没有方法承载新读入的数据的时;对于accept,个别是服务器刚启动的时候;而对于connect,个别是connect失败须要重连或者间接异步调用connect的时候。 其次,用一个死循环抉择就绪的事件,会执行零碎调用(Linux 2.6之前是select、poll,2.6之后是epoll,Windows是IOCP),还会阻塞的期待新事件的到来。新事件到来的时候,会在selector上注册标记位,标示可读、可写或者有连贯到来。 2.2、Reactor模式Reactor模式称之为响应器模式,通常用于NIO非阻塞IO的网络通信框架中。Reactor设计模式用于解决由一个或多个客户端并发传递给应用程序的的服务申请,能够了解成,Reactor模式是用来实现网络NIO的形式。 Reactor是一种事件驱动机制,是解决并发I/O常见的一种模式,用于同步I/O,其中心思想是将所有要解决的I/O事件注册到一个核心I/O多路复用器上,同时主线程阻塞在多路复用器上,一旦有I/O事件到来或是准备就绪,多路复用器将返回并将相应I/O事件散发到对应的处理器中。 Reactor模式次要分为上面三个局部: 事件接收器Acceptor:次要负责接管申请连贯,接管申请后,会将建设的连贯注册到分离器中。事件分离器Reactor:依赖于循环监听多路复用器Selector,是阻塞的,一旦监听到事件,就会将事件散发到事件处理器。(例如:监听读事件,等到内核态数据就绪后,将事件散发到Handler,Handler将数据读到用户态再做解决)事件处理器Handler:事件处理器次要实现相干的事件处理,比方读写I/O操作。2.3、三种Reactor模式单线程Reactor模式一个线程: 单线程:建设连贯(Acceptor)、监听accept、read、write事件(Reactor)、处理事件(Handler)都只用一个单线程。多线程Reactor模式一个线程 + 一个线程池: 单线程:建设连贯(Acceptor)和 监听accept、read、write事件(Reactor),复用一个线程。工作线程池:处理事件(Handler),由一个工作线程池来执行业务逻辑,包含数据就绪后,用户态的数据读写。主从Reactor模式三个线程池: 主线程池:建设连贯(Acceptor),并且将accept事件注册到从线程池。从线程池:监听accept、read、write事件(Reactor),包含期待数据就绪时,内核态的数据I读写。工作线程池:处理事件(Handler),由一个工作线程池来执行业务逻辑,包含数据就绪后,用户态的数据读写。3、Tomcat线程模型3.1、Api网络申请过程咱们先补一下基础知识,解说后端接口的响应过程。一个http连贯里,残缺的网络处理过程个别分为accept、read、decode、process、encode、send这几步: accept:接管客户端的连贯申请,创立socket连贯(tcp三次握手,创立连贯)。read:从socket读取数据,包含期待读就绪,和理论读数据。decode:解码,因为网络上的数据都是以byte的模式进行传输的,要想获取真正的申请,必然须要解码。process:业务解决,即服务端程序的业务逻辑实现。encode:编码,同理,因为网络上的数据都是以byte的模式进行传输的,也就是socket只接管byte,所以必然须要编码。send:往网络socket写回数据,包含理论写数据,和期待写就绪。3.2、各个线程模型在tomcat的各个版本中,所反对的线程模型也产生了一步步演变。一方面,间接将默认线程模型,从BIO变成了NIO。另一方面,在后续几个版本中,退出了对AIO和APR线程模型的反对,这里要留神,仅仅是反对,而非默认线程模型。 BIO:阻塞式IO,tomcat7之前默认,采纳传统的java IO进行操作,该模式下每个申请都会创立一个线程,实用于并发量小的场景。NIO:同步非阻塞,比传统BIO能更好的反对大并发,tomcat 8.0 后默认采纳该模式。AIO:异步非阻塞 (NIO2),tomcat8.0后反对。多用于连贯数目多且连贯比拟长(重操作)的架构,比方相册服务器,充沛调用OS参加并发操作,编程比较复杂。APR:tomcat 以JNI模式调用http服务器的外围动态链接库来解决文件读取或网络传输操作,须要编译装置APR库(也就是说IO操作的局部间接调用native代码实现)。各个线程模型中,NIO是作为目前最实用的线程模型,因而也是目前Tomcat默认的线程模型,因而本文对此着重解说。 ...

September 22, 2020 · 1 min · jiezi