上篇文章介绍了堆外内存DirectByteBuffer,我们知道了DirectByteBuffer是分配在JVM堆外的ByteBuffer,这篇文章来了解堆内内存HeapByteBuffer。HeapByteBufferHeapByteBuffer,即分配在JVM中的heap堆中的ByteBuffer,调用ByteBuffer#allocate()即可生成一个HeapByteBuffer对象。publicstaticByteBufferallocate(intcapacity){if(capacity<0)thrownewIllegalArgumentException();return
前面9篇文章我们已经深入了解了NIO的基本概念和核心原理,想必小伙伴们对NIO的三大组件已经了然于心了,对于ByteBuffer而言,其实还有两个较为特殊的类DirectByteBuffer和MappedByteBuffer没有分析,这两个类的原理都是基于内存文件映射的。ByteBuffer分为两种,一种是直接的,另外一种是间接的。直接缓冲:直接使用内存映射,对于Java而言就是直接在JVM之外分配虚拟内存地址空间,Java中使用DirectByteBuffer来实现,也就是堆外内存。间接缓冲:是在JVM堆上实现,Java中使用HeapByteBuffer来实现,也就是堆内内存。我们这篇文章主
多线程架构前面所有文章的示例服务端都是单线程模式,这种模式存在很多的缺陷无法充分利用多核CPU的性能如果服务端某个读写操作耗时较多,则会拖慢整个服务端所以,这篇文章大明哥将介绍服务端多线程的模式,让我们榨干服务器性能。我们清楚服务端主要做两件事,建立连接和处理读写事件,所以我们可以将服务端的线程分为两组:一个线程专门处理accept事件,我们称之为Boss线程CPU核心数个线程,这些线程处理读写事件,我们称之为Worker线程这个时候,客户端服务端的关系如下:Boss线程只服务处理Acept事件,Worker线程只处理读写事件,他们都各自维护一个Selector,每个Selector负责监听自
背景我们先看下面一段代码。publicstaticvoidmain(String[]args)throwsException{ServerSocketChannelserverSocketChannel=ServerSocketChannel.open();serverSocketChannel.configureBlocking(false);serverSocketChannel.bind(newInetSocketAddress(8081));Selectorselector=Selector.open();serverSocketChannel.register(selector,Se
上篇文章(【死磕NIO】—详解Selector)详细介绍了Selector的核心原理和使用方法,这篇文章我们来深入了解Selector的源码,主要讲三个最常用的方法open(),register()和selector()。open()调用Selector.open()方法会创建一个Selector实例。publicstaticSelectoropen()throwsIOException{returnSelectorProvider.provider().openSelector();}SelectorProvider是一个抽象类,它提供了创建Selector、ServerSocketChan
前面4篇文章深入分析了NIO三大组件中的两个:Buffer和Channel:【死磕NIO】—深入分析Buffer【死磕NIO】—深入分析Channel和FileChannel【死磕NIO】—跨进程文件锁:FileLock【死磕NIO】—探索SocketChannel的核心原理这篇文章则介绍第三个组件:Selector。相比Buffer和Channel而言,Selector对于NIO来说显得更加重要,因为它是NIO实现多路复用的核心,它的使命就是完成IO的多路复用。Selector简介在前一篇文章:【死磕NIO】—ServerSocketChannel的应用实例,大明哥分析了ServerSock
出自:http://ifeve.com/java-nio-all/JavaNIO管道是2个线程之间的单向数据连接。Pipe有一个source通道和一个sink通道。数据会被写到sink通道,从source通道读取。这里是Pipe原理的图示:创建管道通过Pipe.open()方法打开管道。例如:Pipepipe=Pipe.open();向管道写数据要向管道写数据,需要访问sink通道。像这样:Pipe.SinkChannelsinkChannel=pipe.sink();通过调用SinkChannel的write()方法,将数据写入SinkChannel,像这样:StringnewData=&q
出自:http://ifeve.com/java-nio-all/JavaNIO中的DatagramChannel是一个能收发UDP包的通道。因为UDP是无连接的网络协议,所以不能像其它通道那样读取和写入。它发送和接收的是数据包。打开DatagramChannel下面是DatagramChannel的打开方式:DatagramChannelchannel=DatagramChannel.open();channel.socket().bind(newInetSocketAddress(9999));这个例子打开的DatagramChannel可以在UDP端口9999上接收数据包。接收数据通过r
出自:http://ifeve.com/java-nio-all/即使你知道JavaNIO非阻塞的工作特性(如Selector,Channel,Buffer等组件),但是想要设计一个非阻塞的服务器仍然是一件很困难的事。非阻塞式服务器相较于阻塞式来说要多上许多挑战。本文将会讨论非阻塞式服务器的主要几个难题,并针对这些难题给出一些可能的解决方案。查找关于非阻塞式服务器设计方面的资料实在不太容易,所以本文提供的解决方案都是基于本人工作和想法上的。如果各位有其他的替代方案或者更好的想法,我会很乐意听取这些方案和想法!你可以在文章下方留下你的评论,或者发邮件给我(邮箱为:info@jenkov.com)
出自:http://ifeve.com/java-nio-all/JavaNIO中的ServerSocketChannel是一个可以监听新进来的TCP连接的通道,就像标准IO中的ServerSocket一样。ServerSocketChannel类在java.nio.channels包中。这里有个例子:ServerSocketChannelserverSocketChannel=ServerSocketChannel.open();serverSocketChannel.socket().bind(newInetSocketAddress(9999));while(true){SocketCh
出自:http://ifeve.com/java-nio-all/JavaNIO中的SocketChannel是一个连接到TCP网络套接字的通道。可以通过以下2种方式创建SocketChannel:打开一个SocketChannel并连接到互联网上的某台服务器。一个新连接到达ServerSocketChannel时,会创建一个SocketChannel。打开SocketChannel下面是SocketChannel的打开方式:SocketChannelsocketChannel=SocketChannel.open();socketChannel.connect(newInetSocketAd
出自:http://ifeve.com/java-nio-all/JavaNIO中的FileChannel是一个连接到文件的通道。可以通过文件通道读写文件。FileChannel无法设置为非阻塞模式,它总是运行在阻塞模式下。打开FileChannel在使用FileChannel之前,必须先打开它。但是,我们无法直接打开一个FileChannel,需要通过使用一个InputStream、OutputStream或RandomAccessFile来获取一个FileChannel实例。下面是通过RandomAccessFile打开FileChannel的示例:RandomAccessFileaFil
出自:http://ifeve.com/java-nio-all/Selector(选择器)是JavaNIO中能够检测一到多个NIO通道,并能够知晓通道是否为诸如读写事件做好准备的组件。这样,一个单独的线程可以管理多个channel,从而管理多个网络连接。为什么使用Selector?仅用单个线程来处理多个Channels的好处是,只需要更少的线程来处理通道。事实上,可以只用一个线程处理所有的通道。对于操作系统来说,线程之间上下文切换的开销很大,而且每个线程都要占用系统的一些资源(如内存)。因此,使用的线程越少越好。但是,需要记住,现代的操作系统和CPU在多任务方面表现的越来越好,所以多线程的开
出自:http://ifeve.com/java-nio-all/在JavaNIO中,如果两个通道中有一个是FileChannel,那你可以直接将数据从一个channel(译者注:channel中文常译作通道)传输到另外一个channel。transferFrom()FileChannel的transferFrom()方法可以将数据从源通道传输到FileChannel中(译者注:这个方法在JDK文档中的解释为将字节从给定的可读取字节通道传输到此通道的文件中)。下面是一个简单的例子:RandomAccessFilefromFile=newRandomAccessFile("fromFi
出自:http://ifeve.com/java-nio-all/JavaNIO开始支持scatter/gather,scatter/gather用于描述从Channel(译者注:Channel在中文经常翻译为通道)中读取或者写入到Channel的操作。分散(scatter)从Channel中读取是指在读操作时将读取的数据写入多个buffer中。因此,Channel将从Channel中读取的数据“分散(scatter)”到多个Buffer中。聚集(gather)写入Channel是指在写操作时将多个buffer的数据写入同一个Channel,因此,Channel将多个Buffer中的数据“聚集
出自:http://ifeve.com/java-nio-all/JavaNIO中的Buffer用于和NIO通道进行交互。如你所知,数据是从通道读入缓冲区,从缓冲区写入到通道中的。缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存。这块内存被包装成NIOBuffer对象,并提供了一组方法,用来方便的访问该块内存。Buffer的基本用法使用Buffer读写数据一般遵循以下四个步骤:写入数据到Buffer调用flip()方法从Buffer中读取数据调用clear()方法或者compact()方法当向buffer写入数据时,buffer会记录下写了多少数据。一旦要读取数据,需要通过flip(
出自:http://ifeve.com/java-nio-all/JavaNIO的通道类似流,但又有些不同:既可以从通道中读取数据,又可以写数据到通道。但流的读写通常是单向的。通道可以异步地读写。通道中的数据总是要先读到一个Buffer,或者总是要从一个Buffer中写入。正如上面所说,从通道读取数据到缓冲区,从缓冲区写入数据到通道。如下图所示:Channel的实现这些是JavaNIO中最重要的通道的实现:FileChannelDatagramChannelSocketChannelServerSocketChannelFileChannel从文件中读写数据。DatagramChannel能通
出自:http://ifeve.com/java-nio-all/JavaNIO由以下几个核心部分组成:ChannelsBuffersSelectors虽然JavaNIO中除此之外还有很多类和组件,但在我看来,Channel,Buffer和Selector构成了核心的API。其它组件,如Pipe和FileLock,只不过是与三个核心组件共同使用的工具类。因此,在概述中我将集中在这三个组件上。其它组件会在单独的章节中讲到。Channel和Buffer基本上,所有的IO在NIO中都从一个Channel开始。Channel有点象流。数据可以从Channel读到Buffer中,也可以从Buffer写到
迟来的总结,NIO系列写了11篇了,本篇做个总结吧写这个系列的起因是各个框架比如netty,tomcat,jetty这些高性能框架的基石就是NIO,一直想讲讲它们高性能的原因。1.NIO网络编程体系本系列主要是讲NIO网络编程相关的,因为Java服务端开发最关心的就是这些。我们记不住孤立的事实,知识得体系化.这里上一张笔者写本系列画的脑图笔者先从Unix网络编程开始,讲了5种IO模型,阻塞I/O(blockingI/O)非阻塞I/O(non-blockingI/O)I/O复用(I/Omultiplexing)信号驱动式I/O(signal-drivenI/O)异步I/O(asynchronou
前面已经讲了Selector,SocketChannel和DirectBuffer,这些是NIO网络编程中最核心的组件接下来我们会再讲几点非核心的优化(不代表不重要,只是API不占NIO设计的大头):文件传输(FileTransfer):文件内容直接发送到网卡,或者从网卡直接读到文件里内存映射文件(Memory-mappedFiles):将文件的一块映射到内存这两项本质上都基于零拷贝(zerocopy)技术。1.零拷贝?1.1简介零拷贝(Zero-Copy)是指计算机在执行操作时,CPU不需要先将数据从某处内存复制到一个特定区域,从而节省CPU时钟周期和内存带宽—-维基百科拿常用的网络文件传输
前面我们详细讲了Java-NIO分析-8-Selector详解和Java-NIO分析-9-从BSD-socket到SocketChannel,分别是NIO的事件分发器和非阻塞处理器.为了支持Channel的双向读写和Scatter/Gather操作,我们还需要Buffer,将I/O数据存储备用。普通的Buffer都是JVM堆内的Buffer,比较好理解.接下来我们聊聊JVM使用堆外内存的沧桑历史以及为什么要设计出DirectBuffer。首先我们要从没有NIO的前JDK1.4时代开始说起,那会儿大家使用的是SocketInputStream和SocketOutputStream.1.Java传
前面我们讲了高并发核心Selector的源码分析,看到其对操作系统I/O多路复用的简单封装。有了I/O多路复用之后,我们还需要非阻塞的socket读写操作.因为内核告诉你A连接有数据可读,你想要读1K,事实上只读到了0.5K,如果使用传统的socketAPI,那么进程或者线程会在这里阻塞,浪费了CPU的时钟周期和珍贵的线程资源。使用非阻塞就能在没有读满之前立刻返回,数据先放内存里,然后继续读下一个B连接的数据。SocketChannel就是NIO对于非阻塞socket操作的支持的组件,其在socket上封装了一层,所以我们先从SocketAPI说起。1.大名鼎鼎的Socket简介在Java-N
上节Java-NIO分析-7-NIO核心分析之Channel-Buffer和Selector介绍了Channel,Buffer和Selector的基本用法有了感性认识之后,来看看Selector的底层是如何实现的。1.Selector设计笔者下载得是openjdk8的源码,画出类图比较清晰得看到,openjdk中Selector的实现是SelectorImpl,然后SelectorImpl又将职责委托给了具体的平台,比如图中框出的linux2.6以后才有的EpollSelectorImpl,Windows平台则是WindowsSelectorImpl,MacOSX平台是KQueueSelect
上次Java-NIO分析-6-Java-NIO中的概念讲到了NIO的设计思想,即DougLea大佬受AWT启发得到的事件驱动机制,关键点在于非阻塞处理器事件分发组件在NIO的API中,Channel就是实现非阻塞的组件,而事件分发(Dispatcher)使用的是Selector组件,在传统的I/O流(Stream)是有方向的,而NIO支持双向读写,这样就需要将流中的数据读取到某个缓冲组件里,即Buffer组件.Buffer组件还有个特殊的实现DirectByteBuffer,可以申请堆外内存,关于为什么要申请堆外内存后续会谈。1.ChannelChannel是NIO中用来实现非阻塞数据操作的桥
前面介绍了Unix的I/O模型以及多路复用的c实现,为什么要介绍这些呢?因为JVM是用c++写的,JDK的native方法也都是用c写的,最后它们调用的还是操作系统底层的api,所以了解一些关键的底层原理还是有必要的。讲Java的NIO之前,先讲些基础知识.1.基础知识1.1阻塞和非阻塞阻塞和非阻塞关注的是应用进程在等待调用结果时的状态,如果应用进程在等结果的时候将自己挂起,直到得到结果再返回,这是阻塞调用如果应用进程在等结果的时候不会将自己挂起就返回,这是非阻塞调用。举个栗子,你去买星巴克,如果你点了杯咖啡然后一直等着啥也不干直到咖啡送到你手上,这叫阻塞等待,如果你点了杯咖啡后去刷刷微博,然
1.epoll概念poll系统调用相比于select主要解决了文件描述符的数量限制,但是在高并发场景下没有解决根本问题:fd数组整体在内核空间和用户空间之间拷贝遍历整个fd数组找事件浪费资源这俩性能问题在Banga在1999年写了篇论文AScalableandExplicitEventDeliveryMechanismforUNIX,提出select和poll都是无状态的,需要用户空间的进程自行遍历查找事件,一种改进方案是内核内部自己维护事件集合.通过一个类似declare_interest的系统调用,内核能够增量得更新进程感兴趣的事件集合列表,应用进程通过使用get_next_event调用
poll系统调用主要解决了select系统调用的2个问题:文件描述符数量(fd_setsize=32)太小,而且数值是使用宏写死的,这样在32位机器上最大文件描述符数量只有32*32=1024文件描述符集(fd_set)这种值-结果参数的api设计不是很好,select系统调用的时候要分别传读set,写set,更多事件不好细分poll系统调用使用了pollfd数据结构来表示事件数组,没有了fd_setsize的限制,同时支持更多的事件类型1.pollfd结构pollfd的结构是:structpollfd{intfd;//需要检查的fdshortevents;//该fd感兴趣的事件shortre
前面讲了一些Java-NIO分析-2-I-O多路复用历史杂谈,谈到了多路复用的发展历史以及为什么需要它。今天讲广受各大内核支持的select系统调用,select允许进程指定内核等待1个或者多个事件的任何一个发生,并且只在有它们发生之后或者等待一段时间后才唤醒进程。1.select介绍使用select,我们即使应用进程里只有1个线程也能够接受多个连接并且做出处理,因为应用进程不需要阻塞在socket的read,write或者accept系统调用上,而是内核告诉应用有事件到来了,应用进程遍历fd_set看是哪个fd就绪了,然后再处理fd_set称为描述符集,通常是一个整数数组,其中每个整数的每一
前面Java-NIO分析-1-Unix网络模型讲过5种经典I/O模型,现代企业的场景一般是高并发高流量,长连接,假设硬件资源充足,如何提高应用单机能接受链接的上限?先讲段历史UNIX的出现20世纪60年代中期,那会儿还是批处理任务的天下,也就是有一堆job一个个顺序做,一个做完了才做下一个.举个栗子,你没法边听音乐边写博客,也没法边下边播x老师的电影.随后分时这革命性的理念提出来了,每个job只允许占有一小段CPU时间片执行代码,假如cpu处理的够快,看起来就像是一堆job并行一样。分时理念无疑极大地减少了写代码和获取代码执行结果的时间,到了70年代,有人提出要发明一种更好的,多用户的,分时的
要谈Java的NIO,还是先从I/O开始说起。Unix提供了5种不同的I/O模型,分别是阻塞I/O(blockingI/O)非阻塞I/O(non-blockingI/O)I/O复用(I/Omultiplexing)信号驱动式I/O(signal-drivenI/O)异步I/O(asynchronousI/O)一个I/O操作需要从用户态进入内核态运行,通常包括俩阶段等待数据从内核向进程复制数据对于socketI/O而言,第一步通常是等待数据从网络中到达,到达之后会复制到内核的某个缓冲区第二布就是从内核缓冲区复制到应用进程缓冲区阻塞I/O默认情况下,所有的socket都是阻塞的.如图所示应用发起r