不正确使用线程池导致java堆溢出java.lang.OutOfMemoryError: Java heap space

用java写大文件处理,为了提高性能使用了线程池,运行时间长之后会报java.lang.OutOfMemoryError: Java heap space  错误!

报错的代码贴出来:

public void parseReqLog(final String table){
		File[] reqPaths = reqPath();
		// 创建线程池
		ExecutorService pool = Executors.newFixedThreadPool(20);
		for (File req : reqPaths) {
			try {
		        int bufSize = 1048576;// 一次读取的字节长度
				FileChannel fileChannel = new RandomAccessFile(req,"r").getChannel(); 
				ByteBuffer rBuffer = ByteBuffer.allocate(bufSize); // 读取设置长度的字符
				//使用temp字节数组用于存储不完整的行的内容  
		        byte[] temp = new byte[0];  
		        byte[] bs = new byte[0];  
		        while(fileChannel.read(rBuffer) != -1) { 
		        	long start = System.currentTimeMillis();
		        	// position读取结束后的位置,相当于读取的长度
		        	bs = new byte[rBuffer.position()]; //用来存放读取的内容的数组
		        	rBuffer.rewind(); //将position设回0,所以你可以重读Buffer中的所有数据,此处如果不设置,无法使用下面的get方法  
		        	rBuffer.get(bs); //相当于rBuffer.get(bs,0,bs.length()):从position初始位置开始相对读,读bs.length个byte,并写入bs[0]到bs[bs.length-1]的区域 
		        	rBuffer.clear(); //这块的内容已经放入到bs,rbuffer清空
//		        	// 开始处理已经读取到的内容
		        	int startNum = 0;  
	                int LF = 10;//换行符  
	              //判断是否出现了换行符,注意这要区分LF-\n,CR-\r,CRLF-\r\n,这里判断\n 
	                boolean hasLF = false;//是否有换行符
	                for(int i=0;i < bs.length;i++) {  
	                    if(bs[i] == LF) {  
	                    	hasLF = true;  
	                        int lineNum = i - startNum;
	                        byte[] lineByte = new byte[temp.length+lineNum];  
		                    System.arraycopy(temp,0,lineByte,0,temp.length);  
		                    temp = new byte[0];  
		                    System.arraycopy(bs,startNum,lineByte,temp.length,lineNum);  
		                    final String line = new String(lineByte);
		                    lineByte = new byte[0];
		                    try {
		                    	pool.execute(new Runnable() {
									public void run() {
										parseLine(line, table);
										System.gc();
									}
								});
							} catch (Exception e) {
								e.printStackTrace();
							}
		                    
		                    startNum = i;
	                    }  
	                } 
	                if (hasLF) {
	                    //将换行符之后的内容(去除换行符)存到temp中  
	                    temp = new byte[bs.length-startNum-1];  
	                    System.arraycopy(bs,startNum+1,temp,0,bs.length-startNum-1); 
					} else {  
		                //如果没出现换行符,则将内容保存到temp中  
		                byte[] toTemp = new byte[temp.length + bs.length];  
		                System.arraycopy(temp, 0, toTemp, 0, temp.length);  
		                System.arraycopy(bs, 0, toTemp, temp.length, bs.length);  
		                temp = toTemp;  
		            }  
	                long end = System.currentTimeMillis();
	        		System.out.println("读取该段耗时 :"+(end - start)+"ms");
	        		System.gc();
	        		displayAvailableMemory();// 输出当前占用内存数
	                
		        }
		        fileChannel.close();
			} catch (IOException e) {
				log.error(req+",文件解析失败:"+e.getMessage());
				continue;
			}
		}
		// 关闭线程池
		pool.shutdown();
	}

文件大小为1G左右,刚开始认为是读取文件的时候导致的堆内存溢出,故每次操作完一行之后调用gc,然后输出内存。观察发现没有效果,堆内存一直在增加。一行行排除得出结论,有可能是线程问题,监控线程占用内存数,发现线程每次执行完一样会释放,最后把疑点放到了线程池上。


研究线程池原理,发现创建线程池时线程的阻塞队列默认是先进先出的链表队列,如果不设置大小,默认为int的max值。线程的处理速度没有nio读取速度快,导致阻塞队列一直在增加,最后的结果就是内存溢出。

修整后的代码:

// 创建线程池
		ThreadPoolExecutor pool = new ThreadPoolExecutor(100, 100,
                0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue(10),new ThreadPoolExecutor.CallerRunsPolicy());
设置队列大小,修改任务拒绝策略。每行必须处理故选择 由调用线程处理该任务!


你可能感兴趣的:(不正确使用线程池导致java堆溢出java.lang.OutOfMemoryError: Java heap space)