java.io.IOException: Too many open files问题

在开发linux在线服务器的时候经常会遇会句柄泄露的问题。因为在linux系统设计里面遵循一切都是文件的原则,即磁盘文件、目录、网络套接字、磁盘、管道等,所有这些都是文件,在我们进行打开的时候会返回一个fd,即是文件句柄。如果频繁的打开文件,或者打开网络套接字而忘记释放就会有句柄泄露的现象。在linux系统中对进程可以调用的文件句柄数进行了限制,在默认情况下每个进程可以调用的最大句柄数是1024个,如果超过了这个限制,进程将无法获取新的句柄,而从导致不能打开新的文件或者网络套接字,对于线上服务器即会出现服务被拒绝的情况。

查看句柄:

在linux系统中可以通过ulimit–n查看每个进程限制的最大句柄数,通过ulimit –HSn 10240修改进程的最大句柄数。当句柄数目达到限制后,就回出现”too many files open”。

查看进程占用的句柄数有几种办法:

1) 通过cat/proc/pid/fd可以查看线程pid号打开的线程,cat   /proc/pid/fd |wc -l;

2) 通过lsof命令,需要root账号权限

查看当前系统的打开文件数:

# lsof | wc -l  

# watch "lsof | wc -l"

可以用lsof -p <pid of process>看打开的文件句柄数.查看某个进程的打开文件数

#lsof -p  1234|wc -l

 

查看Linux系统打开的文件句柄数:

# lsof -n | awk '{print $2}'| sort | uniq -c | sort -nr 

   3009 31312
     15 7364
     14 8002
     14 7777
     14 7382
     14 665
     14 6308
     ...
第一列:进程打开的文件句柄数
第二列:PID

 

应用日志报java.io.IOException: Too many open files

打开的文件过多,一般来说是由于应用程序对资源使用不当造成,比如没有及时关闭Socket或数据库连接等。但也可能应用确实需要打开比较多的文件句柄,而系统本身的设置限制了这一数量。

 

异常 1 
java.net.SocketException: Too many open files

    at java.net.PlainSocketImpl.accept(Compiled Code)
    at java.net.ServerSocket.implAccept(Compiled Code)
    at java.net.ServerSocket.accept(Compiled Code)
    at weblogic.t3.srvr.ListenThread.run(Compiled Code)

异常 2
java.io.IOException:打开的文件过多

    at java.lang.UNIXProcess.forkAndExec(Native Method)
    at java.lang.UNIXProcess.(UNIXProcess.java:54)
    at java.lang.UNIXProcess.forkAndExec(Native Method)
    at java.lang.UNIXProcess.(UNIXProcess.java:54)
    at java.lang.Runtime.execInternal(Native Method)
    at java.lang.Runtime.exec(Runtime.java:551)
    at java.lang.Runtime.exec(Runtime.java:477)
    at java.lang.Runtime.exec(Runtime.java:443)

...

第一个异常在错误影响到基础 TCP 协议时抛出,而第二个异常则在错误影响到 I/O 操作时抛出,这个是由于交换分区不足造成的。

文件打开数过多最坏的情况可以使系统崩溃,到时候只能是重起服务器了。 

 

原因:  

 1、大多数情况是程序没有正常关闭一些资源引起的,所以出现这种情况,请检查io读写,socket通讯等是否正常关闭。

  操作系统的中打开文件的最大句柄数受限所致,常常发生在很多个并发用户访问服务器的时候。因为为了执行每个用户的应用服务器都要加载很多文件(new一个socket就需要一个文件句柄),这就会导致打开文件的句柄的缺乏。 

       主要方向:在源码项目中查找所有引用了InputStream或者其他流的地方,查看其是否在使用完后,正常close掉,此处建议将流的close放到finally块中,这样异常时也会去close流。

       次要方向:session或者其他有用到socket的代码处,仔细查询看是否有没有释放资源的地方

 
   2、 如果检查程序没有问题,那就有可能是linux默认的open files值太小,不能满足当前程序默认值的要求,比如数据库连接池的个数,tomcat请求连接的个数等。。。 

         限制:有时是linux系统的参数的限制,需要修改内核参数。有时是中间件(weblogicnginx)中启动文件参数限制,尤其是默认1024,在修改linux后若中间件有涉及也要同步修改。

      

 

解决:

尽量把类打成jar包,因为一个jar包只消耗一个文件句柄,如果不打包,一个类就消耗一个文件句柄.
java的垃圾回收不能关闭网络连接打开的文件句柄,如果没有执行close()(例如:java.net.Socket.close())则文件句柄将一直存在,而不能被关闭.你也可以考虑设置socket的最大打开数来控制这个问题.
对操作系统做相关的设置,增加最大文件句柄数量。


Linux
在 Linux内核2.4.x中需要修改源代码,然后重新编译内核才生效。编辑Linux内核源代码中的 include/linux/fs.h文件,将 NR_FILE 由8192改为65536,将NR_RESERVED_FILES 由10 改为 128。编辑fs/inode.c 文件将MAX_INODE 由16384改为262144。或者编辑 /etc/sysctl.conf  文件增加两行 fs.file-max = 65536 和 fs.inode-max = 262144 。一般情况下,系统最大打开文件数比较合理的设置为每4M物理内存256,比如256M.

 

在linux下的fd是有限制的,一个是系统的限制,一个是用户进程限制。

 

服务器端修改:

查看系统允许打开的最大文件数

#cat /proc/sys/fs/file-max

sysctl fs.file-max=655360可以调整内核的阈值,前提得有root权限。可参考/etc/sysctl.conf,用man sysctl.conf命令sysctl -a可以显示所有的能够调整参数。

 

查看每个用户允许打开的最大文件数

ulimit -a

发现系统默认的是open files (-n) 1024,问题就出现在这里。

在系统文件/etc/security/limits.conf中修改这个数量限制,

在文件中加入内容:

* soft nofile 65536
* hard nofile 65536

 

另外方法:
1.使用ps -ef |grep java   (java代表你程序,查看你程序进程) 查看你的进程ID,记录ID号,假设进程ID为12
2.使用:lsof -p 12 | wc -l    查看当前进程id为12的 文件操作状况
    执行该命令出现文件使用情况为 1052
3.使用命令:ulimit -a   查看每个用户允许打开的最大文件数
    发现系统默认的是open files (-n) 1024,问题就出现在这里。
4.然后执行:ulimit -n 4096
     将open files (-n) 1024 设置成open files (-n) 4096

这样就增大了用户允许打开的最大文件数

 

同时查看ulimit实际是否生效,如果设置了但并未生效也不行。

运维实战案例之“Too many open files”错误与解决方法 

此文中就介绍了设置了参数,但并未生效引起的。

 

 

socket未关闭现象

1. 首先用lsof -p PID 查看一下打开文件的列表如果出现下图状况基本就两种可能,stream未关闭或者socket未关闭,出现can't identify protocol字样

2. 用netstat -anp | grep PID查看端口占用情况,若出现下图情况,证明Socket未关闭

原因:

  是因为Socket协议本身,若正确关闭一个Socket需要往返消息4次,若中途有未接到的消息就会停留在某个状态下,比如当前的CLOSE_WAIT。

        如果close_wait状态过多,可能是socket未关闭引起。

解决办法:

  客户端:主动关闭任何一个socket,尽量在finally中加入socket.close()语句。

  服务器:设置读取超时时间如:socket.setSoTimeout(3000);其次在用完之后主动关闭,做法同客户端方式。同时在任何的异常加入socket.shutdownInput();socket.shutdownOutput();

 

问题定位

问题定位步骤:
1、   用root帐户 遍历 /proc/进程ID/fd目录,若该目录下文件数较大(如果大于10一般就属于socket泄漏),根据该进程ID,可以确认该进程ID所对应的名称。
2、   重启程序恢复服务,以便后续查找问题。
3、   strace 该程序并记录strace信息。strace –p 进程ID >>/tmp/stracelog.log 2>&1
4、   查看 /proc/进程ID/fd 下的文件数目是否有增加,如果发现有增加,记录上一个socket编号,停止strace
5、   确认问题代码的位置。打开/tmp/stracelog.log,从尾部向上查找close(socket编号)所在行,可以确认在该次close后再次创建的socket没有关闭,根据socket连接的server ip可以确认问题代码的位置。


另一种方法:判断是否有socket泄漏:
lsof | grep "can't identify protocol"
如果存在很多,则代表socket泄漏,同时会显示哪个进程使用的sock未关闭。

 

文件句柄泄露定位方法:

1、使用lsof命令查出有哪些文件或者socket被打开。

2、根据文件名找到相应的模块,检查相关的代码.如果是socket,则根据端口号找到相应的模块,最后检查相关的代码。

 

 

 


 

mina高并发短连接导致java.io.IOException: Too many open files解决方案

  1.         //设置超时   
  2.         connector.setConnectTimeoutMillis(defaultConnectTimeOut);  
  3.         ConnectFuture connectFuture = connector.connect(address);  
  4.         connectFuture.awaitUninterruptibly();   //同步,等待,直到连接完成   
  5.         if  (connectFuture.isDone()) {  
  6.             if  (!connectFuture.isConnected()) {  //若在指定时间内没连接成功,则抛出异常   
  7.                 logger.info("fail to connect "  + logInfo);  
  8.   
  9.                 throw   new  Exception();  
  10.             }  
  11.         }  

 

         经过分析,导致主机文件句柄泄露的原因为,客户端发起服务端连接时,会请求系统分配相关的文件句柄,在原代码中,仅仅判断是否连接成功,而未对连接失败进 行资源释放,从而造成文件句柄泄露。当总的文件句柄数超过系统设置值(ulimit -n 查看同一个进程允许的最大文件句柄数),则抛出异常“java.io.IOException: Too many open files",导致无法创建新的连接,服务器挂掉。

      更改后的代码如下:

 

Java代码  收藏代码
  1. final  NioSocketConnector connector =  new  NioSocketConnector();  
  2.         final  String[] result =  new  String[ 1 ];  
  3.         connector.getFilterChain().addLast("codec" ,  
  4.                 new  ProtocolCodecFilter( new  ObjectSerializationCodecFactory()));  
  5.         connector.setHandler(handler);  
  6.   
  7.         //设置超时   
  8.         connector.setConnectTimeoutMillis(defaultConnectTimeOut);  
  9.         ConnectFuture connectFuture = connector.connect(address);  
  10.         connectFuture.awaitUninterruptibly();   //同步,等待,直到连接完成   
  11.         if  (connectFuture.isDone()) {  
  12.             if  (!connectFuture.isConnected()) {  //若在指定时间内没连接成功,则抛出异常   
  13.                 logger.info("fail to connect "  + logInfo);  
  14.                 connector.dispose();    //不关闭的话会运行一段时间后抛出,too many open files异常,导致无法连接   
  15.   
  16.                 throw   new  Exception();  
  17.             }  
  18.         }  

 


 

from http://www.jianshu.com/p/2e6748c4c0b7

 

原因一:Linux 的open files 数不够

[root@chances125 ~]# ulimit -a
core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 78454
max locked memory       (kbytes, -l) 64
max memory size         (kbytes, -m) unlimited
open files                      (-n) 1024
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 10240
cpu time               (seconds, -t) unlimited
max user processes              (-u) 1024
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited
lsof -n |awk '{print $2}'|sort|uniq -c |sort -nr|more
#查一下当前已有的连接数,再来判断open files 为1024 是不是小
lsof -p $java_pid | wc -l
#加大打开的文件数
ulimit -n 2048
#用户级别的修改

#系统级设置对所有用户有效。
#可通过两种方式查看系统最大文件限制                 
cat /proc/sys/fs/file-max          
#修改配置/etc/sysctl.conf文件
fs.file-max=2048
#通知系统启用这项配置 
sysctl -p

 

原因二:TCP 参数配置不对

netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'  
TIME_WAIT 7091
CLOSE_WAIT 2
ESTABLISHED 716
LISTEN 10

常用的三个状态是:ESTABLISHED 表示正在通信,TIME_WAIT 表示主动关闭,CLOSE_WAIT 表示被动关闭
服务器保持了大量TIME_WAIT状态

#参数优化
#表示如果套接字由本端要求关闭,这个参数决定了它保持在FIN-WAIT-2状态的时间
net.ipv4.tcp_fin_timeout = 30
#表示当keepalive起用的时候,TCP发送keepalive消息的频度。缺省是2小时,改为300秒,原因是:当前都是图片,不需要建立长链接
net.ipv4.tcp_keepalive_time = 300
#表示开启SYN Cookies。当出现SYN等待队列溢出时,启用cookies来处理,可防范少量SYN攻击,默认为0,表示关闭
net.ipv4.tcp_syncookies = 1
#表示如果套接字由本端要求关闭,这个参数决定了它保持在FIN-WAIT-2状态的时间
net.ipv4.tcp_tw_reuse = 1
#表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭,当前TIME-WAIT 过多,所以开启快速回收
net.ipv4.tcp_tw_recycle = 1
net.ipv4.ip_local_port_range = 5000 65000

配置生效后,观察结果,TIME_WAIT 明显减少。

TIME_WAIT 2492
CLOSE_WAIT 10
ESTABLISHED 730
LISTEN 10

原因三:Tomcat 配置不对

connectionTimeout - 网络连接超时,单位:毫秒。设置为0表示永不超时,这样设置有隐患的。通常可设置为30000毫秒。
当前配置connectionTimeout=0

根据公式:服务器端最佳线程数量=((线程等待时间+线程cpu时间)/线程cpu时间) * cpu数量

待测试验证

 

原因四:程序打开了许多文件,但未关闭连接

之前一直在找程序哪里打开了文件却未关闭连接,未果。
才想到连接打开后,多久可以关闭。

lsof -i :80
COMMAND   PID   USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
java    3011 root  837u  IPv6            3513129        0t0     TCP sc-epg-app15:8085->10.223.52.16:38494 (ESTABLISHED)
java    3011 root  838u  IPv6              31731        0t0     TCP sc-epg-app15:8085->10.230.160.147:38903 (ESTABLISHED)
java    3011 root  839u  IPv6            3667505        0t0     TCP sc-epg-app15:8085->10.251.207.71:35411 (ESTABLISHED)
#NODE 对应的是TCP
#PID 3011,当前服务器上只有一个应用。

待解决的问题是:
1)如何定位到程序代码什么地方打开了文件?
2)打开的这些文件是不是都是必要的?
3)can't identify protocol的原因


 

参考资料:

1、http://www.2cto.com/kf/201212/175863.html

2、http://marsvaadin.iteye.com/blog/1698924

3、MINA2 错误解决方法-- Linux下tomcat报错“java.net.SocketException: Too many open files”

4、tcp socket文件句柄泄漏

 

posted @ 2014-05-18 20:57  milkty  阅读(12317)  评论(0编辑  收藏  举报