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系统的参数的限制,需要修改内核参数。有时是中间件(weblogic、nginx)中启动文件参数限制,尤其是默认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解决方案
- //设置超时
- connector.setConnectTimeoutMillis(defaultConnectTimeOut);
- ConnectFuture connectFuture = connector.connect(address);
- connectFuture.awaitUninterruptibly(); //同步,等待,直到连接完成
- if (connectFuture.isDone()) {
- if (!connectFuture.isConnected()) { //若在指定时间内没连接成功,则抛出异常
- logger.info("fail to connect " + logInfo);
- throw new Exception();
- }
- }
经过分析,导致主机文件句柄泄露的原因为,客户端发起服务端连接时,会请求系统分配相关的文件句柄,在原代码中,仅仅判断是否连接成功,而未对连接失败进 行资源释放,从而造成文件句柄泄露。当总的文件句柄数超过系统设置值(ulimit -n 查看同一个进程允许的最大文件句柄数),则抛出异常“java.io.IOException: Too many open files",导致无法创建新的连接,服务器挂掉。
更改后的代码如下:
- final NioSocketConnector connector = new NioSocketConnector();
- final String[] result = new String[ 1 ];
- connector.getFilterChain().addLast("codec" ,
- new ProtocolCodecFilter( new ObjectSerializationCodecFactory()));
- connector.setHandler(handler);
- //设置超时
- connector.setConnectTimeoutMillis(defaultConnectTimeOut);
- ConnectFuture connectFuture = connector.connect(address);
- connectFuture.awaitUninterruptibly(); //同步,等待,直到连接完成
- if (connectFuture.isDone()) {
- if (!connectFuture.isConnected()) { //若在指定时间内没连接成功,则抛出异常
- logger.info("fail to connect " + logInfo);
- connector.dispose(); //不关闭的话会运行一段时间后抛出,too many open files异常,导致无法连接
- throw new Exception();
- }
- }
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”