码迷,mamicode.com
首页 > 编程语言 > 详细

Java NIO1:概述

时间:2015-10-21 22:24:46      阅读:241      评论:0      收藏:0      [点我收藏+]

标签:

前言

相比I/O,NIO更复杂、更不好理解,因此在开始NIO之前,需要讲解一些概念,如果对于这些概念有着良好的理解,对于学习NIO绝对是有好处的。

 

同步与异步

所谓同步就是指一个任务的完成需要依赖另外一个任务时,只有等待被依赖的任务完成后,依赖的任务才能完成,这是一种可靠的任务序列。要成功都成功,要失败都失败,两个任务的状态可以保持一致。

而异步不需要等待被依赖的任务完成,只是通知被依赖的任务要完成什么工作,依赖的任务也立即执行,只要自己完成了整个任务就算完成了。至于被依赖的任务最终是否真正完成,依赖它的任务无法确定,所以它是不可靠的任务序列。

在涉及I/O处理时通常都会遇到是同步还是异步的处理方式的选择问题,因为同步与异步的I/O处理方式对调用者的影响很大,在数据库产品中都会遇到这个问题,因为I/O操作通常是一个非常耗时的操作,在一个任务序列中I/O通常都是性能瓶颈。但是同步与异步的处理方式对程序的可靠性影响非常大,同步能够保证程序的可靠性,而异步可以提升程序的性能,必须在可靠性和性能之间保持平衡,却没有完美的解决办法。

 

阻塞与非阻塞

阻塞与非阻塞是从CPU的消耗上来说的。

阻塞就是CPU停下来等待一个慢的操作完成以后,CPU才接着完成其他的工作,在执行这个慢的操作的过程中CPU需要不断轮询操作是否完成。

非阻塞就是在这个慢的操作执行时,CPU去做其他工作,等这个慢的操作完成了,CPU再接着完成后续的操作。

虽然从表面上看非阻塞的方式可以明显地提高CPU的利用率,但是也带来了另外一种后果,就是系统的线程切换的增加,增加的CPU使用时间能不能补偿系统的切换成本需要好好评估。

 

CPU已不再是束缚

Java程序员把全部精力用在优化处理效率上,而对I/O关注不足,在某种程度上并非他们的错。在Java早期,JVM在解释字节码时往往很少或没有运行时优化,这就意味着,Java程序往往拖得很长,其运行效率大大低于本地编译代码,因而对操作系统I/O子系统的要求不太高。

如今在运行时优化方面,JVM已经前进了一大步,现在JVM运行字节码的速率已经接近本地编译代码,借助动态运行时优化,其表现甚至还有所超越。这就意味着,多数Java应用程序已经不再受CPU的束缚(把大量时间用在执行代码上),而更多时候是受I/O的束缚(等待数据传输)。

然而,在大多数情况下,Java应用程序并非真的受着I/O的束缚。操作系统并非无法快速传送数据,让Java有事可做,而是JVM自身在 I/O方面效率欠佳。操作系统与Java基于流的I/O模式有些不匹配。操作系统要移动的是大块数据(缓冲区),这往往是在硬件直接存储器存取(DMA)的协助下完成的。而JVM的I/O类喜欢操作小块数据----单个字节、几行文本。结果,操作系统送来整块缓冲区的数据,java.io的流类再花大量时间把它们拆成小块,往往拷贝一个小块就要往返于几层对象。

有了NIO,就可以轻松把大量数据传输到需要直接使用的地方。不过,这并不是说使用传统的I/O模型无法移动大量数据----当然可以,RandomAccesssFile类就可以,而且效率也不差,只要坚持使用基于数组的read()、write()方法。这些方法与底层操作系统调用相当接近,尽管必须保留至少一份缓冲区拷贝。

 

NIO的提出

JDK1.4的NIO软件包引入了一套新的抽象用于I/O处理。与以往不同的是,新的抽象把重点放在了如何缩短抽象与现实之间的距离上面。NIO抽象与现实中存在的实体有着非常真实直接的交互关系。要想最大限度地满足Java应用程序密集I/O需求,理解这些新的抽象,以及与其发生交互作用的I/O服务,正是关键所在。

在NIO的学习中,理解以下概念是非常重要的:

  • 缓冲区操作
  • 内核空间与用户空间
  • 虚拟内存
  • 分页技术
  • 面向文件的I/O与流I/O
  • 多工I/O

 

缓冲区操作

缓冲区,以及缓冲区如何工作,是所有I/O的基础。所谓"输入/输出"讲的无非就是把数据移入或移出缓冲区。

执行I/O操作,归结起来,也就是向操作系统发出请求,让它要么把缓冲区里的数据读走,要么用数据把缓冲区填满。进程使用这一机制处理所有数据进出操作。操作系统内部处理这一任务的机制,其复杂程序可能超乎想象,但就概念而言,非常简单易懂。

下图描述了数据从外部磁盘向运行中的进程的内存区域移动的过程:

技术分享

 

进程使用read()系统调用,要求其缓冲区被填满。内核随机向磁盘控制硬件发出命令,要求其从磁盘读取数据。磁盘控制器把数据写入内核内存缓冲区,这一步通过DMA完成,无需主CPU协助。一旦磁盘控制器把缓冲区填满,内核即把数据从内核空间的临时缓冲区拷贝到进程read()调用时指定的缓冲区。

注意用户空间和内核空间的概念。用户空间是常规进程所在的区域,JVM就是常规进程,驻守于用户空间。用户空间是非特权区域:比如在该区域执行的代码就不能直接访问硬件设备。内核空间是操作系统所在的区域。内核代码有特别的权利:它能与设备控制器通讯,控制着用户区域进程的运行状态等。最重要的是,所有I/O都直接或间接通过内核空间。

当进程请求I/O操作的时候,它执行一个系统调用将控制权移交给内核。C/C++程序员熟知的底层函数open()、read()、write()、close()要做的无非就是建立和执行适当的系统调用。党内和以这种方式被盗用,它随即采取任何必要步骤,找到进程所需数据,并把数据传送到用户空间内的指定缓冲区。内核试图对数据进行高速缓存或预读取,因此进程所需数据可能已经在内核空间里了。如果是这样,改数据只需要简单拷贝出来即可。如果数据不在内核空间,则进程被挂起,内核着手把数据读进内存。

可能有人会觉得,为什么不直接让磁盘控制器把数据传送到用户空间的缓冲区呢?这样做有几个问题:

1、硬件通常无法直接访问用户空间

2、像磁盘这样基于块存储的硬件设备操作的是固定大小的数据块,而用户进程请求的可能是任意大小的或非对其的数据块。在数据往来于用户空间和存储设备的过程中,内核负责数据的分解、再组合工作

 

虚拟内存

所有现代操作系统都使用虚拟内存。虚拟内存意为使用虚假(或虚拟)地址取代物理(硬件RAM)内存地址。这样做好处颇多,总结起来可以分为:

1、一个以上的虚拟地址可指向同一个物理内存地址

2、虚拟内存空间可大于实际可用的硬件内存

前面提到,控制设备器不能通过DMA直接存储到用户空间,但通过利用上面提到的第一项,则可以达到相同效果。把内核空间地址与用户空间的虚拟地址映射到同一个物理地址,这样,DMA硬件(只能访问物理内存地址)就可以填充对内核与用户空间进程同时可见的缓冲区了:

技术分享

 

这样真的太好了,省去了内核空间与用户空间的往来拷贝,但前提是,内核与用户缓冲区必须使用相同的页对齐,缓冲区的大小还必须是磁盘控制器块大小的倍数。

 

Java NIO1:概述

标签:

原文地址:http://www.cnblogs.com/xrq730/p/4896485.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!