在開發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”