新blog位址: http://hengyunabc.github.io/netstat-difference-proc-fd-socket-stat/
最近,線上一個應用,發現socket數緩慢增長,并且不回收,超過警告線之後,被運維監控自動重新開機了。
首先到zabbix上觀察JVM曆史記錄,發現JVM-Perm space最近兩周沒有資料,猜測是程式從JDK7切換到JDK8了。問過開發人員之後,程式已經很久沒有重新開機了,最近才重新釋出的。而在這期間,線上的Java運作環境已經從JDK7更新到JDK8了。
因為jdk8裡沒有Perm space了,換成了Metaspace。
netstat
到線上伺服器上,用netstat來統計程序的connection數量。
netstat -antp | grep pid | wc -l
發現比zabbix上的統計socket數量要少100多,netstat統計隻有100多,而zabbix上監控資料有300多。
于是到/proc/$pid/fd下統計socket類型的fd數量:
cd /proc/$pid/fd
ls -al | grep socket | wc -l
發現資料和zabbix上的資料一緻。
netstat是怎麼統計的
下載下傳netstat的源代碼
http://unix.stackexchange.com/questions/21503/source-code-of-netstatapt-get source net-tools
從netstat的代碼裡,大概可以看到是讀取/proc/net/tcp裡面的資料來擷取統計資訊的。
java和c版的簡單netstat的實作
java版的
http://www.cs.earlham.edu/~jeremiah/LinuxSocket.javaC版的:
http://www.netmite.com/android/mydroid/system/core/toolbox/netstat.c用starce跟蹤netstat
strace netstat -antp
可以發現netstat把/proc 下的很多資料都讀取出來了。于是大緻可以知道netstat是把/proc/pid/fd 下面的資料和/proc/net/下面的資料彙總,對照得到統計結果的。
哪些socket會沒有被netstat統計到?
又在網上找了下,發現這裡有說到socket如果建立了,沒有bind或者connect,就不會被netstat統計到。
http://serverfault.com/questions/153983/sockets-found-by-lsof-but-not-by-netstat實際上,也就是如果socket建立了,沒有被使用,那麼就隻會在/proc/pid/fd下面有,而不會在/proc/net/下面有相關資料。
簡單測試了下,的确是這樣:
int socket = socket(PF_INET,SOCK_STREAM,0); //不使用
另外,即使socket是使用過的,如果執行shutdown後,剛開始裡,用netstat可以統計到socket的狀态是FIN_WAIT1。過一段時間,netstat統計不到socket的資訊的,但是在/proc/pid/fd下,還是可以找到。
中間的時候,自己寫了個程式,把/proc/pid/fd 下的inode和/proc/net/下面的資料比較,發現的确有些socket的inode不會出現在/proc/net/下。
用lsof檢視
用lsof檢視socket inode:
觸發GC,回收socket
于是嘗試觸發GC,看下socket會不會被回收:
jmap -histo:live <pid>
結果,發現socket都被回收了。
再看下AbstractPlainSocketImpl的finalize方法:
/**
* Cleans up if the user forgets to close it.
*/
protected void finalize() throws IOException {
close();
}
可以看到socket是會在GC時,被close掉的。
寫個程式來測試下:
public class TestServer {
public static void main(String[] args) throws IOException, InterruptedException {
for(int i = 0; i < 10; ++i){
ServerSocket socket = new ServerSocket(i + 10000);
System.err.println(socket);
}
System.in.read();
}
}
先執行,檢視/proc/pid/fd,可以發現有相關的socket fd,再觸發GC,可以發現socket被回收掉了。
其它的東東
anon_inode:[eventpoll]
ls -al /proc/pid/fd
可以看到有像這樣的輸出:
661 -> anon_inode:[eventpoll]
這種類型的inode,是epoll建立的。
再扯遠一點,linux下java裡的selector實作是epoll結合一個pipe來實作事件通知功能的。是以在NIO程式裡,會有anon_inode:[eventpoll]和pipe類型的fd。
為什麼tail -f /proc/$pid/fd/1 不能讀取到stdout的資料
http://unix.stackexchange.com/questions/152773/why-cant-i-tail-f-proc-pid-fd-1總結
原因是jdk更新之後,GC的工作方式有變化,FullGC執行的時間變長了,導緻有些空閑的socket沒有被回收。
本文比較亂,記錄下一些工具和技巧。