java套接字创建失败_Linux的文件描述符个数限制导致创建文件(或socket)失败的问题...

本文介绍了Linux中由于文件描述符(fd)数量限制导致的Java套接字创建失败问题。通过分析/proc/$pid/fd目录下socket的inode号,结合/proc/net/tcp文件,可以了解套接字状态。解决方案包括调整系统和进程的最大打开文件描述符限制。遇到fd数量过多导致的错误,可通过检查FDSize、实际打开的fd数,以及监控进程的socket使用情况来排查问题。
摘要由CSDN通过智能技术生成

众所周知,在相应进程的/proc/$pid/fd 目录下存放了此进程所有打开的fd。当然有些可能不是本进程自己打开的,如通过fork()从父进程继承而来的。本文着着重讲述socket有关的内容。当我们在fd目录下使用 ls -l 命令查看时,会看到诸如下面的内容:

lrwx------ 1 root root 64 Nov 21 09:44 133 -> /dev/sda1

lrwx------ 1 root root 64 Nov 21 09:44 134 -> /dev/sdb1

lrwx------ 1 root root 64 Nov 21 09:44 136 -> /dev/sdb1

lrwx------ 1 root root 64 Nov 21 09:44 137 -> socket:[22460]

lrwx------ 1 root root 64 Nov 21 09:44 138 -> socket:[7326842]

lrwx------ 1 root root 64 Nov 21 09:44 139 -> socket:[7341066]

那么这个socket:后面的一串数字是什么呢?其实是该socket的inode号。从linux内核代码net/socket.c 中可以看出,如下

/*

* sockfs_dname() is called from d_path().

*/

static char *sockfs_dname(struct dentry *dentry, char *buffer, int buflen)

{

return dynamic_dname(dentry, buffer, buflen, "socket:[%lu]",

dentry->d_inode->i_ino);

}

那么,知道了某个进程打开的socket的inode号后,我们可以做什么呢?这就涉及到/proc/net/tcp(udp对应/proc/net/udp)文件了,其中也列出了相应socket的inode号通过比对此字段,我们能在/proc/net/tcp下获得此套接口的其他信息,如对应的对,窗口大小,状态等信息。具体字段含义详见net/ipv4/tcp_ipv4.c 中的 tcp4_seq_show 函数。cat /proc/net/tcp 如下:

sl  local_address rem_address   st tx_queue rx_queue tr tm->when retrnsmt   uid  timeout inode

19: 0100007F:83B8 0100007F:A57D 01 00000000:00000000 00:00000000 00000000     0        0 10879 1 f622edc0 20 4 31 3 -1

20: 0100007F:0FA0 0100007F:AA06 01 00000000:00000000 00:00000000 00000000     0      0 7326842 1 f5504dc0 20 4 11 5 -1

碰到一个文件fd数量不足,导致socket创建失败的问题。

而文件描述符即fd个数其实分为两种,一种是系统本身的总的限制个数,另一种是进程能够打开的具体的限制的个数。

系统最大打开文件描述符数:/proc/sys/fs/file-max

a.    查看

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

2. 设置

a.    临时性

# echo 1000000 > /proc/sys/fs/file-max

2.    永久性:在/etc/sysctl.conf中设置

fs.file-max = 1000000

2.    进程最大打开文件描述符数:user limit中nofile的soft limit

a.    查看

$ ulimit -n

1800000

2. 设置

a.    临时性:通过ulimit -Sn设置最大打开文件描述符数的soft limit,注意soft limit不能大于hard limit(ulimit -Hn可查看hard limit),另外ulimit -n默认查看的是soft limit,但是ulimit -n 1800000则是同时设置soft limit和hard limit。对于非root用户只能设置比原来小的hard limit。

查看hard limit:

$ ulimit -Hn

1700000

设置soft limit,必须小于hard limit:

$ ulimit -Sn 1600000

2.    永久性:上面的方法只是临时性的,注销重新登录就失效了,而且不能增大hard limit,只能在hard limit范围内修改soft limit。若要使修改永久有效,则需要在/etc/security/limits.conf中进行设置(需要root权限),可添加如下两行,表示用户chanon最大打开文件描述符数的soft limit为1800000,hard limit为2000000。以下设置需要注销之后重新登录才能生效:

chanon           soft    nofile          1800000

chanon           hard   nofile          2000000

设置nofile的hard limit还有一点要注意的就是hard limit不能大于/proc/sys/fs/nr_open,假如hard limit大于nr_open,注销后无法正常登录。可以修改nr_open的值:

# echo 2000000 > /proc/sys/fs/nr_open

3.    查看当前系统使用的打开文件描述符数

[root@localhost bin]# cat /proc/sys/fs/file-nr

5664        0        186405

其中第一个数表示当前系统已分配使用的打开文件描述符数,第二个数为分配后已释放的(目前已不再使用),第三个数等于file-max。

4.    总结:

a.    所有进程打开的文件描述符数不能超过/proc/sys/fs/file-max

b.    单个进程打开的文件描述符数不能超过user limit中nofile的soft limit

c.    nofile的soft limit不能超过其hard limit

d.    nofile的hard limit不能超过/proc/sys/fs/nr_open

但是这只是查看限制个数。

实际在查看是否是fd打开数量太多导致失败,还需要从进程中去查看,其方法:

1.当前进程分配过的文件描述符的近似的最高值

cat  /proc/进程号/status

在这个文件中会有一个FDSize字段,该字段是表示当前进程分配过的文件描述符的近似的最高值。为什么是近似,因为这里FDSize本身是不会减少的,如果刚开始打开了18个文件,则这里的FDSize等于32,若大于32,则以32为单位递增,例如33则是64;这个32的单位是依赖于系统位数的,如果是32位则是32,若是64位系统则是以64倍增。

2.实际的已经打开的fd

ls -l  /proc/进程号/fd | wc

可以统计实际打开的fd个数

在碰到的这个实际问题中,相关进程的FDSize一起来就已经到了1024,而用ulimit -n查看进程打开的fd限制刚好是1024,再用/proc/进程号/fd中去查看个数是五百多个,这时还是未真正出错的时候。但是相对来讲未起业务就已经开了很多fd了,正常运行业务需要大量socket进行传输,自然就出现了fd不够的情况。

新blog地址:

最近,线上一个应用,发现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的源代码

apt-get source net-tools

从netstat的代码里,大概可以看到是读取/proc/net/tcp里面的数据来获取统计信息的。

java和c版的简单netstat的实现

java版的

C版的:

用starce跟踪netstat

strace netstat -antp

可以发现netstat把/proc 下的很多数据都读取出来了。于是大致可以知道netstat是把/proc/pid/fd 下面的数据和/proc/net/下面的数据汇总,对照得到统计结果的。

哪些socket会没有被netstat统计到?

又在网上找了下,发现这里有说到socket如果创建了,没有bind或者connect,就不会被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

结果,发现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的数据

总结

原因是jdk升级之后,GC的工作方式有变化,FullGC执行的时间变长了,导致有些空闲的socket没有被回收。

本文比较乱,记录下一些工具和技巧。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值