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文件句柄泄漏

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM