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。
- java nio多线程引起的full gc问题
- Java -- ExecutorService线程池触发的Full GC问题排查
- 并发环境下HashMap引起的full gc排查
- 并发环境下HashMap引起的full gc排查
- 并发环境下HashMap引起的full gc排查
- 详解Java GC的工作原理+Minor GC、Full GC
- 用Java获取full GC的次数
- gc cr引起的数据库性能问题
- 【java基础】Minor GC、Major GC和Full GC之间的区别
- 我遇到tomcat 7 full gc频繁的问题
- 我遇到tomcat 7 full gc频繁的问题
- Java程序持续Full GC的处理经历(转)
- 案例分析:java中substring引发的Full gc
- Java深入 - 触发Full GC执行的情况
- java-jvm-full gc频繁的分析及解决
- 记一次惨痛的java服务器full gc.......经过
- Major GC和Full GC的区别
- Major GC和Full GC的区别
- zoj 3612 (multiset)
- Oracle BBED模拟坏块&验证坏块
- Redis 缓存 + Spring 的集成示例
- jquery-easyui简单使用
- 安卓开发学习笔记(一)
- java nio多线程引起的full gc问题
- leetcode happy number
- 并查集的路径压缩
- 匿名类,try-catch语句
- iOS--错误集锦--申请公司开发者账号遇到的相关问题
- ubuntu和windows间互传文件
- margin-top越界以及所有子元素浮动后父元素高度为0且影响后续元素的问题。
- 云计算的三种服务模式:IaaS,PaaS和SaaS
- 牛客堂刷题(常见面试题精讲)之最长回文序列