java nio多线程引起的full gc问题

1.在写nio的例子时,服务端采用线程池处理请求,遇到一个full gc问题,下面给代码贴出来。
nioserver端代码

package com.nio.study;

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.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 类NIOServer.java的实现描述:TODO 类实现描述
 * 
 * @author macun 2015年11月29日 下午4:18:46
 */
public class NIOServer {

    private static final int DEFAULT_PORT = 8888;
    private static Selector  selector;
    private Object           lock = new Object();
    private Boolean          isStart      = false;
    private ExecutorService  threadPool = null;

    public void init() throws IOException {
        selector = Selector.open();

        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        ServerSocket ss = serverChannel.socket();
        ss.bind(new InetSocketAddress(DEFAULT_PORT));
        serverChannel.configureBlocking(false);
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);
        threadPool = Executors.newFixedThreadPool(10);
//        threadPool = new ThreadPoolExecutor(10, 10,
//                                            1000L, TimeUnit.MILLISECONDS,
//                                            new ArrayBlockingQueue(100));
        synchronized (lock) {
            isStart = true;
        }
        System.out.println("nio server init successful.");
    }

    public void run() throws IOException {
        synchronized (lock) {
            if (!isStart) {
                try {
                    init();
                } catch (IOException e) {
                    throw new IOException("nio server init failed.");
                }
            }
        }

        while (true) {
            selector.select();

            Iterator iterator = selector.selectedKeys().iterator();
            while(iterator.hasNext()){
                SelectionKey key = (SelectionKey) iterator.next();
                iterator.remove();

                if (key.isAcceptable()) {
                    ServerSocketChannel server = (ServerSocketChannel) key.channel();
                    SocketChannel client = server.accept();
                    client.configureBlocking(false);
                    SelectionKey clientKey = client.register(selector, SelectionKey.OP_READ);
                    ByteBuffer buffer = ByteBuffer.allocate(100);
                    clientKey.attach(buffer);
                }
                if (key.isReadable()) {

                    NIOHandler handler = new NIOHandler(key);
                    threadPool.execute(handler);

                }
            }
        }
    }
}

NIOHandler代码

package com.nio.study;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;

/**
 * 类NIOHandler.java的实现描述:TODO 类实现描述
 * 
 * @author macun 2015年11月29日 下午4:40:35
 */
public class NIOHandler implements Runnable {

    private SelectionKey key;

    public NIOHandler(SelectionKey key){
        this.key = key;
    }

    @Override
    public void run() {
        System.out.println("handler:"+key);
        SocketChannel client = (SocketChannel) key.channel();
        ByteBuffer buffer = (ByteBuffer) key.attachment();
        try {
            int n = client.read(buffer);
            String temp = null;
            if (n > 0) {
                temp = new String(buffer.array());
                System.out.println(temp);
                buffer.clear();

                buffer.put(temp.getBytes());
                buffer.flip();
                client.write(buffer);
                buffer.clear();
            }

//              key.cancel();
//            client.close();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        } catch (IOException e) {
            try {
                client.close();
            } catch (IOException e1) {
                // TODO Auto-generated catch block
                e1.printStackTrace();
            }
            System.out.println(e.getMessage());
        }

    }

    public void setSelectionKey(SelectionKey key) {
        this.key = key;
    }

}

Server 启动测试类

public class NIOServerTest {
    public static void main(String[] args){
        NIOServer server = new NIOServer();
        try {
            server.run();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

client端10个socket请求

public class NIOClient1 {

    public static final String IP_ADDR = "localhost"; // ip adress
    public static final int    PORT    = 8888;       // listen port

    public static void main(String[] args) throws Exception {

        System.out.println("client start .....");
        int i = 1;
        Socket socket = null;
        for (int j = 0; j < 20; j++) {
            try {
                socket = new Socket(IP_ADDR, PORT);
                DataInputStream input = new DataInputStream(socket.getInputStream());
                DataOutputStream out = new DataOutputStream(socket.getOutputStream());

                String str = "send " + i + " message.";
                i++;
                out.writeUTF(str);
                String ret = input.readUTF();
                System.out.println("from server:" + ret);

                socket.close();

//                Thread.sleep(1000);
            } catch (Exception e) {
                System.out.println("client " + e.getMessage());
            } finally {
                if (socket != null) {
                    try {
                        socket.close();
                    } catch (IOException e) {
                        socket = null;
                        System.out.println("client finally " + e.getMessage());
                    }

                }

            }

        }
    }

}

2.在没有用线程池的时候,发现正常;当用线程池处理的时候,发现程序卡死了。一看cpu使用率超过300%,再看full gc一直释放不掉垃圾。
问题现象:full gc,并且一直释放不掉垃圾。
分析:
1.在Handler处理逻辑部分,Thread.sleep(1000),让其休眠1秒钟。
2.线程池是由Executors工厂生成的10个固定大小的线程池和无界队列。
3.Selector监听是无条件循环的。
由以上3个条件猜测是10个固定大小的线程池在等待1秒的时候,Selector无条件循环接受到的SelectionKey放入到无界队列里,导致队列爆满,并且这些又是强引用,fullgc无法释放,导致越积越多,程序卡死。
猜测验证,把无界队列换成有界队列ArrayBlockingQueue,大小1000,很快就会抛出异常,包队列大小不够。
解决方法:
1.无界队列改成有界队列
2.因为selector是无条件循环,当SelectionKey的channel读写没有完成,它会一直取出来。handler里sleep(1000)的时候,它的SelectionKey仍然会被NIOServer 里的selector读取,那么就需要保持SelectionKey不被重复读取。
3.在handler完成后,需要把SelectionKey 从Holder里remove,并cancel掉。
基于以上3点,针对 NIOServer和Handler类进行改造,代码如下:
NIOServer代码片段更改如下

//        threadPool = Executors.newFixedThreadPool(10);
        threadPool = new ThreadPoolExecutor(10, 10,
                                            1000L, TimeUnit.MILLISECONDS,
                                            new ArrayBlockingQueue(100));

SelectionKeyHolder保持SelectionKey

if (key.isReadable()) {
                    if(SelectionKeyHolder.isContainKey(key)){
                        continue;
                    }
                    SelectionKeyHolder.put(key);
                    System.out.println(key);
                    NIOHandler handler = new NIOHandler(key);
                    threadPool.execute(handler);

SelectionKeyHolder类,代码片段如下:

import java.nio.channels.SelectionKey;
import java.util.HashSet;

/**
 * 类SelectionKeyHolder.java的实现描述:TODO 类实现描述
 * 
 * @author macun 2015年11月30日 下午12:21:26
 */
public class SelectionKeyHolder {

    private static HashSet keySet = new HashSet();

    private static Object                lock   = new Object();

    public static void put(SelectionKey key) {
        synchronized (lock) {
            keySet.add(key);
        }

    }

    public static  boolean isContainKey(SelectionKey key) {
        return keySet.contains(key);
    }

    public static void putIfAbsent(SelectionKey key) {
        if (keySet.contains(key)) {
            return;
        }
        put(key);
    }

    public static void remove(SelectionKey key) {
        synchronized (lock) {
            keySet.remove(key);
        }

    }
}

Handler类改造

public void run() {
        System.out.println("handler:"+key);
        SocketChannel client = (SocketChannel) key.channel();
        ByteBuffer buffer = (ByteBuffer) key.attachment();
        try {
            int n = client.read(buffer);
            String temp = null;
            if (n > 0) {
                temp = new String(buffer.array());
                System.out.println(temp);
                buffer.clear();

                buffer.put(temp.getBytes());
                buffer.flip();
                client.write(buffer);
                buffer.clear();
            }
            SelectionKeyHolder.remove(key);
              key.cancel();
//            client.close();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        } catch (IOException e) {
            try {
                client.close();
            } catch (IOException e1) {
                // TODO Auto-generated catch block
                e1.printStackTrace();
            }
            System.out.println(e.getMessage());
        }

    }

通过以上几个重构点,就可以解决以上nio服务端多线程full gc的问题。但是对于使用HashSet保持SelectionKey并不是最优的,可以替换成CurrentHashMap。

你可能感兴趣的:(java知识)