JAVA NIO存在的问题

简介:

    JAVA 包含最新的版本JDK1.8的NIO存在一些问题,这些问题需要在编写NIO程序时要格外关注:

NIO跨平台和兼容性问题

    NIO是底层API,它的实现依赖于操作系统针对IO操作的APIs. 所以JAVA能在所有操作系统上实现统一的接口,并用一致的行为来操作IO是很伟大的。

    使用NIO会经常发现代码在Linux上正常运行,但在Windows上就会出现问题。所以编写程序,特别是NIO程序,需要在程序支持的所有操作系统上进行功能测试,否则你可能会碰到一些莫明的问题。

    NIO2看起来很理想,但是NIO2只支持Jdk1.7+,若你的程序在Java1.6上运行,则无法使用NIO2。另外,Java7的NIO2中没有提供DatagramSocket的支持,所以NIO2只支持TCP程序,不支持UDP程序

NIO对缓冲区的聚合和分散操作可能会导致内存泄露

    很多Channel的实现支持Gather和Scatter。这个功能允许从从多个ByteBuffer中读入或写入,这样做可以有更好的性能。

    操作系统底层知道如何处理这些被写入/读出,并且能以最有效的方式处理。如果要分割的数据在多个不同的 ByteBuffer中,使用Gather/Scatter是比较好的方式。

    例如,你可能希望header在一个ByteBuffer中,而body在另外的ByteBuffer中;
    下图显示的是Scatter(分散),将ScatteringByteBuffer中的数据分散读取到多个ByteBuffer中:
    

    下图显示的是Gather(聚合),将多个ByteBuffer的数据写入到GatheringByteChannel:
    

    可惜Gather/Scatter功能会导致内存泄露,知道Java7才解决内存泄露问题。使用这个功能必须小心编码和Java版本

Squashing the famous epoll bug(压碎著名的epoll bug)

    Linux-like OSs的选择器使用的是epoll-IO事件通知工具。操作系统使用这一高性能的技术与网络协议栈异步工作。
    不幸的是,即使是现在,著名的epoll-bug也可能会导致无效的状态选择和100%的CPU利用率。要解决epoll-bug的唯一方法是回收旧的选择器,将先前注册的通道实例转移到新创建的选择器上。

    NIO中对epoll问题的解决方案是有限制的,Netty提供了更好的解决方案。下面是epoll-bug的一个例子

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

public class PlainNioEchoServer {
    public static void main(String[] args) throws IOException {
        serve(8118);
    }


    public static void serve(int port) throws IOException {
        System.out.println("Listening for connections on port " + port);
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        ServerSocket ss = serverChannel.socket();
        InetSocketAddress address = new InetSocketAddress(port);
        ss.bind(address);
        serverChannel.configureBlocking(false);
        Selector selector = Selector.open();
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);
        while (true) {
            try {
                // 这里发生的是,不管有没有已选择的SelectionKeySelector.select()方法总是不会阻塞并且会立刻返回。
                // 这违反了Javadoc中对Selector.select()方法的描述,
                // Javadoc中的描述:Selector.select() must not unblock if nothing is selected.
                // (Selector.select()方法若未选中任何事件将会阻塞。)
                System.out.println(".............");
                selector.select();
            }
            catch (IOException ex) {
                ex.printStackTrace();
                // handle in a proper way
                break;
            }
            Set readyKeys = selector.selectedKeys();
            Iterator iterator = readyKeys.iterator();
            // 该值将永远是假的,代码将持续消耗你的CPU资源。
            //这会有一些副作用,因为CPU消耗完了就无法再去做其他任何的工作。
            while (iterator.hasNext()) {
                SelectionKey key = (SelectionKey) iterator.next();
                iterator.remove();
                try {
                    if (key.isAcceptable()) {
                        ServerSocketChannel server = (ServerSocketChannel) key.channel();
                        SocketChannel client = server.accept();
                        System.out.println("Accepted connection from " + client);
                        client.configureBlocking(false);
                        client.register(selector, SelectionKey.OP_WRITE | SelectionKey.OP_READ,
                            ByteBuffer.allocate(100));
                    }
                    if (key.isReadable()) {
                        SocketChannel client = (SocketChannel) key.channel();
                        ByteBuffer output = (ByteBuffer) key.attachment();
                        client.read(output);
                    }
                    if (key.isWritable()) {
                        SocketChannel client = (SocketChannel) key.channel();
                        ByteBuffer output = (ByteBuffer) key.attachment();
                        output.flip();
                        client.write(output);
                        output.compact();
                    }
                }
                catch (IOException ex) {
                    key.cancel();
                    try {
                        key.channel().close();
                    }
                    catch (IOException cex) {
                    }
                }
            }
        }
    }
}

   运行程序后,客户端连接进来,什么工作都不做,但CPU利用率却已经达到100%

    

     这些仅仅是在使用NIO时可能会出现的一些问题。不幸的是,虽然在这个领域发展了多年,问题依然存在;

    幸运的是,Netty给了你比较好的解决方案

目录
相关文章
|
1月前
|
存储 Java 数据处理
|
1月前
|
Java API
java中IO与NIO有什么不同
java中IO与NIO有什么不同
|
2月前
|
移动开发 编解码 网络协议
用Java的BIO和NIO、Netty来实现HTTP服务器(三) 用Netty实现
用Java的BIO和NIO、Netty来实现HTTP服务器(三) 用Netty实现
|
2月前
|
网络协议 Java Linux
用Java来实现BIO和NIO模型的HTTP服务器(二) NIO的实现
用Java来实现BIO和NIO模型的HTTP服务器(二) NIO的实现
|
2月前
|
编解码 网络协议 Java
用Java的BIO和NIO、Netty实现HTTP服务器(一) BIO与绪论
用Java的BIO和NIO、Netty实现HTTP服务器(一) BIO与绪论
|
3月前
|
Java 应用服务中间件 Linux
java中的NIO,BIO,AIO
java中的NIO,BIO,AIO
16 0
|
3月前
|
设计模式 网络协议 Java
Java NIO 网络编程 | Netty前期知识(二)
Java NIO 网络编程 | Netty前期知识(二)
73 0
|
3月前
|
Java 索引
📌 Java NIO Buffer
Java NIO缓冲区在与NIO通道交互时使用。数据从通道读取到缓冲区,然后从缓冲区写入通道。 缓冲区本质上是一块内存,可以在其中写入数据,然后再进行读取。这个内存块被封装在一个NIOBuffer对象中,该对象提供了一组方法,可以更容易地使用内存块。
|
3月前
|
缓存 网络协议 Java
📌 Java NIO Channel
Java NIOChannel和传统的流相似,但是也存在一些差异: • 在同一个Channel通道中,既可以进行 读操作 也可以进行 写操作,但是 流 只能进行 读 或者 写 其中一种操作。 • Channel通道可以进行异步读写。 • Channel可以从 Buffer中进行读写操作。将数据从Channel通道读取到Buffer缓冲区,并将数据从Buffer缓冲区写入Channel通道。
|
3月前
|
Java API 容器
📌 Java NIO
Java NIO(New IO或 Non Blocking IO)是从Java 1.4版本开始引入的一个新的IOAPI,可以替代标准的Java IO API。NIO支持面向缓冲区的、基于通道的IO操作。NIO将以更加高效的方式进行文件的读写操作。(NIO非阻塞 IO操作)。Java NIO包含了如下三个核心组件:Channel、Buffers、Selectors。