Java I/O API之性能分析 (下)(转)
短信预约 -IT技能 免费直播动态提醒
四、注册与处理过程详解
接下来我们要分析Connection的register()方法。前面我们总是说用Selector注册的连接,其实这是一种简化的说法。实际上,用Selector注册的是一个java.nio.channels.SocketChannel对象,但只针对特定的I/O操作。注册之后,有一个java.nio.channels.SelectionKey被返回。这个选择键可以通过attach()方法关联到任意对象。为了通过键获得连接,这里把Connection对象关联到键。这样,我们就可以从Selector间接地获得一个Connection。
public void register(Selector selector)
throws IOException {
key = socketChannel.register(selector, SelectionKey.OP_READ);
key.attach(this);
}
回过头来看ConnectionSelector。select()方法的返回值表示有多少连接已经做好了I/O操作的准备。如果返回值是0,则返回;否则,调用selectedKeys()获得键的集合(Set),从这些键获得以前关联的Connection对象,然后调用其readRequest()或writeResponse()方法,具体调用哪一个方法由连接被注册为读取操作还是写入操作决定。
现在再来看Connection类。Connection类代表着连接,处理所有协议有关的细节。在构造函数中,通过参数传入的SocketChannel被设置成非阻塞模式,这对于服务器来说是很重要的。另外,构造函数还设置了一些默认值,分配了缓冲区requestLineBuffer。由于分配直接缓冲区代价稍高,且这里的每一个连接都用一个新的缓冲区,因此这里使用java.nio.ByteBuffer.allocate()而不是ByteBuffer.allocateDirect()。如果重用缓冲区,直接缓冲区可能具有更高的效率。
public Connection(SocketChannel socketChannel)
throws IOException {
this.socketChannel = socketChannel;
...
socketChannel.configureBlocking(false);
requestLineBuffer = ByteBuffer.allocate(512);
...
}
完成所有初始化工作且SocketChannel做好了读取准备之后,ConnectionSelector调用了readRequest()方法,利用socketChannel.read(requestLineBuffer)方法把所有可用的数据读入缓冲区。如果不能读取完整的行,则返回发出调用的ConnectionSelector,允许另一个连接进入处理过程;反之,如果成功地读取了整个行,接下来应该做的是象在Httpd中一样解析请求。如果当前的请求合法,程序为请求目标文件创建一个java.nio.Channels.FileChannel,并调用prepareForResponse()方法。
private void prepareForResponse() throws IOException {
StringBuffer responseLine = new StringBuffer(128);
...
responseLineBuffer = ByteBuffer.wrap(
responseLine.toString().getBytes("ASCII")
);
key.interestOps(SelectionKey.OP_WRITE);
key.selector().wakeup();
}
prepareForResponse()方法构造出缓冲区responseLine以及(如果必要的话)应答头或错误信息,并把这些数据写入responseLineBuffer。这个ByteBuffer是一个byte数组的简单的封装器。生成待输出的数据之后,我们还要通知ConnectionSelector:从现在开始不再读取数据,而是要写入数据了。这个通知通过调用选择键的interestedOps(SelectionKey.OP_WRITE)方法完成。为了保证选择器能够迅速认识到连接操作状态的变化,接着还要调用wakeup()方法。接下来ConnectionSelector调用连接的writeResponse()方法。首先,responseLineBuffer被写入到Socket管道。如果缓冲区的内容全部被写入,而且还有被请求的文件需要发送,接着调用前面打开的FileChannel的transferTo()方法。transferTo()方法通常能够高效地把数据从文件传输到管道,但实际的传输效率依赖于底层的操作系统。任何时候,被传输的数据量至多相当于在无阻塞的情况下可写入目标管道的数据量。为安全和确保各个连接之间的公平起见,这里把上限设置成64 KB。
如果所有数据都已经传输完毕,close()执行清理工作。取消Connection的注册是这里的主要任务,具体通过调用键的cancel()方法完成。
public void close() {
...
if (key != null) key.cancel();
...
}
这个新的方案性能如何呢?答案是肯定的。从原理上看,一个Acceptor和一个ConnectionSelector足以支持任意数量的打开的连接。因此,新的实现方案在可伸缩性方面占有优势。但是,由于两个线程必须通过同步的queue()方法通信,它们可能互相阻塞对方。解决这个问题有两种途径:
?改进实现队列的方法
?采用多个Acceptor/ConnectionSelector对
与Httpd相比,NIOHttpd的一个缺点是,对于每一个请求,就有一个新的带缓冲的Connection对象被创建。这就导致了垃圾收集器产生的额外的CPU占用,这部分附加代价的具体程度又与VM的类型有关。然而,Sun不厌其烦地强调说,有了Hotspot,短期生存的对象不再成为问题。
五、可伸缩性的定量分析和比较
在可伸缩性方面,NIOHttpd到底比Httpd好多少?下面我们来看看具体的数字。首先要声明的是,这里的数字具有大量的推测成分,一些重要的环境因素,例如线程同步、上下文切换、换页、硬盘速度和缓冲等,都没有考虑到。首先评估处理r个并发的请求需要多少时间,假设被请求的文件大小是s字节,客户端的带宽是b字节/秒。对于Httpd,这个时间显然直接依赖于线程的数量t,因为同一时刻只能处理t个请求。所以Httpd的处理时间可以从公式一得到,其中c是执行请求分析之类操作的开销常量,这个值对于每一个请求来说都是一样的。另外,这里假定从磁盘读取数据的速度总是快于写入Socket的速度,服务器带宽总是大于客户机带宽之和,且CPU未满载。因此,服务器端的带宽、缓冲和硬盘速度等因素都不必在该公式中考虑。
接下来我们要分析Connection的register()方法。前面我们总是说用Selector注册的连接,其实这是一种简化的说法。实际上,用Selector注册的是一个java.nio.channels.SocketChannel对象,但只针对特定的I/O操作。注册之后,有一个java.nio.channels.SelectionKey被返回。这个选择键可以通过attach()方法关联到任意对象。为了通过键获得连接,这里把Connection对象关联到键。这样,我们就可以从Selector间接地获得一个Connection。
public void register(Selector selector)
throws IOException {
key = socketChannel.register(selector, SelectionKey.OP_READ);
key.attach(this);
}
回过头来看ConnectionSelector。select()方法的返回值表示有多少连接已经做好了I/O操作的准备。如果返回值是0,则返回;否则,调用selectedKeys()获得键的集合(Set),从这些键获得以前关联的Connection对象,然后调用其readRequest()或writeResponse()方法,具体调用哪一个方法由连接被注册为读取操作还是写入操作决定。
现在再来看Connection类。Connection类代表着连接,处理所有协议有关的细节。在构造函数中,通过参数传入的SocketChannel被设置成非阻塞模式,这对于服务器来说是很重要的。另外,构造函数还设置了一些默认值,分配了缓冲区requestLineBuffer。由于分配直接缓冲区代价稍高,且这里的每一个连接都用一个新的缓冲区,因此这里使用java.nio.ByteBuffer.allocate()而不是ByteBuffer.allocateDirect()。如果重用缓冲区,直接缓冲区可能具有更高的效率。
public Connection(SocketChannel socketChannel)
throws IOException {
this.socketChannel = socketChannel;
...
socketChannel.configureBlocking(false);
requestLineBuffer = ByteBuffer.allocate(512);
...
}
完成所有初始化工作且SocketChannel做好了读取准备之后,ConnectionSelector调用了readRequest()方法,利用socketChannel.read(requestLineBuffer)方法把所有可用的数据读入缓冲区。如果不能读取完整的行,则返回发出调用的ConnectionSelector,允许另一个连接进入处理过程;反之,如果成功地读取了整个行,接下来应该做的是象在Httpd中一样解析请求。如果当前的请求合法,程序为请求目标文件创建一个java.nio.Channels.FileChannel,并调用prepareForResponse()方法。
private void prepareForResponse() throws IOException {
StringBuffer responseLine = new StringBuffer(128);
...
responseLineBuffer = ByteBuffer.wrap(
responseLine.toString().getBytes("ASCII")
);
key.interestOps(SelectionKey.OP_WRITE);
key.selector().wakeup();
}
prepareForResponse()方法构造出缓冲区responseLine以及(如果必要的话)应答头或错误信息,并把这些数据写入responseLineBuffer。这个ByteBuffer是一个byte数组的简单的封装器。生成待输出的数据之后,我们还要通知ConnectionSelector:从现在开始不再读取数据,而是要写入数据了。这个通知通过调用选择键的interestedOps(SelectionKey.OP_WRITE)方法完成。为了保证选择器能够迅速认识到连接操作状态的变化,接着还要调用wakeup()方法。接下来ConnectionSelector调用连接的writeResponse()方法。首先,responseLineBuffer被写入到Socket管道。如果缓冲区的内容全部被写入,而且还有被请求的文件需要发送,接着调用前面打开的FileChannel的transferTo()方法。transferTo()方法通常能够高效地把数据从文件传输到管道,但实际的传输效率依赖于底层的操作系统。任何时候,被传输的数据量至多相当于在无阻塞的情况下可写入目标管道的数据量。为安全和确保各个连接之间的公平起见,这里把上限设置成64 KB。
如果所有数据都已经传输完毕,close()执行清理工作。取消Connection的注册是这里的主要任务,具体通过调用键的cancel()方法完成。
public void close() {
...
if (key != null) key.cancel();
...
}
这个新的方案性能如何呢?答案是肯定的。从原理上看,一个Acceptor和一个ConnectionSelector足以支持任意数量的打开的连接。因此,新的实现方案在可伸缩性方面占有优势。但是,由于两个线程必须通过同步的queue()方法通信,它们可能互相阻塞对方。解决这个问题有两种途径:
?改进实现队列的方法
?采用多个Acceptor/ConnectionSelector对
与Httpd相比,NIOHttpd的一个缺点是,对于每一个请求,就有一个新的带缓冲的Connection对象被创建。这就导致了垃圾收集器产生的额外的CPU占用,这部分附加代价的具体程度又与VM的类型有关。然而,Sun不厌其烦地强调说,有了Hotspot,短期生存的对象不再成为问题。
五、可伸缩性的定量分析和比较
在可伸缩性方面,NIOHttpd到底比Httpd好多少?下面我们来看看具体的数字。首先要声明的是,这里的数字具有大量的推测成分,一些重要的环境因素,例如线程同步、上下文切换、换页、硬盘速度和缓冲等,都没有考虑到。首先评估处理r个并发的请求需要多少时间,假设被请求的文件大小是s字节,客户端的带宽是b字节/秒。对于Httpd,这个时间显然直接依赖于线程的数量t,因为同一时刻只能处理t个请求。所以Httpd的处理时间可以从公式一得到,其中c是执行请求分析之类操作的开销常量,这个值对于每一个请求来说都是一样的。另外,这里假定从磁盘读取数据的速度总是快于写入Socket的速度,服务器带宽总是大于客户机带宽之和,且CPU未满载。因此,服务器端的带宽、缓冲和硬盘速度等因素都不必在该公式中考虑。
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341
Java I/O API之性能分析 (下)(转)
下载Word文档到电脑,方便收藏和打印~
下载Word文档
猜你喜欢
Java I/O API之性能分析 (下)(转)
四、注册与处理过程详解 接下来我们要分析Connection的register()方法。前面我们总是说用Selector注册的连接,其实这是一种简化的说法。实际上,用Selector注册的是一个java.nio.channels.Soc
2023-06-03
Java I/O API性能实例分析
本篇内容主要讲解“Java I/O API性能实例分析”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Java I/O API性能实例分析”吧! 一、概述 IO API的可伸缩性对Web应用
2023-06-03
AIX 下磁盘 I/O 性能分析
本文主要介绍在磁盘 I/O 性能的概念以及相关的指标,并介绍 AIX 系统下,衡量和监控磁盘 I/O 性能的方法,以及在 AIX 下,常见的可调整参数。磁盘 I/O 的概念I/O 的概念,从字义来理解就是输入输出。操作系统从上层到底层,各个
2023-06-05
Java I/O 之File类的示例分析
这篇文章主要为大家展示了“Java I/O 之File类的示例分析”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“Java I/O 之File类的示例分析”这篇文章吧。File类Java使用Fil
2023-06-20
如何分析Kata容器的I/O性能
这篇文章给大家介绍如何分析Kata容器的I/O性能,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。下面的所有分析是基于Kata容器1.6.2版本,我们将探讨这个主题,以了解在I/O绑定性能和安全性都是关键需求的环境中使用
2023-06-15
2024-04-02
Java基准性能测试之JMH的示例分析
这篇文章主要为大家展示了“Java基准性能测试之JMH的示例分析”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“Java基准性能测试之JMH的示例分析”这篇文章吧。一、JMH vs JMeterJ
2023-06-20