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

来源:互联网 发布:linux命令 cp r 编辑:程序博客网 时间:2024/05/01 00:19

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<Runnable>(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<SelectionKey> 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<Runnable>(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<SelectionKey> keySet = new HashSet<SelectionKey>();    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。

0 0
原创粉丝点击