使用Java NIO的话, 每个channel都是一个文件,
Channels are analogous to "file descriptors" found in Unix-like operating systems.
而Linux操作系统对最大打开文件数有限制, 如下所示:
$ ulimit -a
...
open files (-n) 1024
...
但发现实际可以创建的channel数远超过1024,直到4000多才达到限制并报错:
Start client 4087
java.net.SocketException: Too many open files
at sun.nio.ch.Net.socket0(Native Method)
at sun.nio.ch.Net.socket(Net.java:423)
at sun.nio.ch.Net.socket(Net.java:416)
at sun.nio.ch.SocketChannelImpl.<init>(SocketChannelImpl.java:104)
at sun.nio.ch.SelectorProviderImpl.openSocketChannel(SelectorProviderImpl.java:60)
at java.nio.channels.SocketChannel.open(SocketChannel.java:142)
在stackoverflow上咨询, 有人答复说是jvm隐式改变了该值。故写了程序验证此说法,发现果然修改了最大文件数。测试代码如下所示:
String [] cmdArray = {"sh","-c","ulimit -n"};
Process p = Runtime.getRuntime().exec(cmdArray);
BufferedInputStream in = new BufferedInputStream(p.getInputStream());
byte[] buf = new byte[1024];
int len = in.read(buf);
System.out.println(new String(buf, 0, len));
实际输出为4096。
至于为什么不到4096才报错,是因为
这表示当前用户的每个进程最多允许同时打开1024个文件,这1024个文件中还得除去每个进程必然打开的标准输入,标准输出,标准错误,服务器监听 socket,进程间通讯的unix域socket等文件,那么剩下的可用于客户端socket连接的文件数就只有大概1024-10=1014个左右。也就是说缺省情况下,基于Linux的通讯程序最多允许同时1014个TCP并发连接。
摘自:http://blog.youkuaiyun.com/guowake/article/details/6615728
那如何验证jvm是否对其做了修改以及于何时做了修改呢?经网友kchr的指点,通过strace工具发现jvm确实对打开文件数做了修改,如下所示:
$ strace -f -o HelloWorld.strace java HelloWorld
Hello World!
$ vi HelloWorld.strace
...
32382 getrlimit(RLIMIT_NOFILE, {rlim_cur=1024, rlim_max=4*1024}) = 0
32382 setrlimit(RLIMIT_NOFILE, {rlim_cur=4*1024, rlim_max=4*1024}) = 0
...
搜索Openjdk hotspot源码,发现在os_linux.cpp中调用了上述setrlimit方法,如下所示:
$ grep -r setrlimit
src/os/linux/vm/os_linux.cpp: // if getrlimit/setrlimit fails but continue regardless.
src/os/linux/vm/os_linux.cpp: status = setrlimit(RLIMIT_NOFILE, &nbr_files);
src/os/linux/vm/os_linux.cpp: perror("os::init_2 setrlimit failed");
其对应的源码为:
4616 if (MaxFDLimit) {
4617 // set the number of file descriptors to max. print out error
4618 // if getrlimit/setrlimit fails but continue regardless.
4619 struct rlimit nbr_files;
4620 int status = getrlimit(RLIMIT_NOFILE, &nbr_files);
4621 if (status != 0) {
4622 if (PrintMiscellaneous && (Verbose || WizardMode))
4623 perror("os::init_2 getrlimit failed");
4624 } else {
4625 nbr_files.rlim_cur = nbr_files.rlim_max;
4626 status = setrlimit(RLIMIT_NOFILE, &nbr_files);
4627 if (status != 0) {
4628 if (PrintMiscellaneous && (Verbose || WizardMode))
4629 perror("os::init_2 setrlimit failed");
4630 }
4631 }
4632 }
与strace的输出也能对应上. 下面进一步确认确实是在此处对打开文件数做的修改。
下载Openjdk, 修改os_linux.cpp源码,如下所示:
//nbr_files.rlim_cur = nbr_files.rlim_max;
nbr_files.rlim_cur = 2048; //显式指定为2048
status = setrlimit(RLIMIT_NOFILE, &nbr_files);
编译hotspot, 然后在对应的jvmg目录里执行
sh hotspot CmdExecute
此时输出为2048. strace的结果为:
strace -f -o cmdexecute.strace sh hotspot CmdExecute
4666 getrlimit(RLIMIT_NOFILE, {rlim_cur=1024, rlim_max=4*1024}) = 0
4666 setrlimit(RLIMIT_NOFILE, {rlim_cur=2*1024, rlim_max=4*1024}) = 0
确实变为指定的2048了。
注:
若指定一个非2次幂的值,如4567, 最终结果为默认的1024,如下strace所示:
8403 getrlimit(RLIMIT_NOFILE, {rlim_cur=1024, rlim_max=4*1024}) = 0
8403 setrlimit(RLIMIT_NOFILE, {rlim_cur=4567, rlim_max=4*1024}) = -1 EINVAL (Invalid argument)
参考文档:
http://man7.org/linux/man-pages/man2/setrlimit.2.html
http://www.ibm.com/developerworks/cn/linux/l-tsl/
补充:
1. 客户端验证最大channel数代码:
for (int i=1;i<4096;i++) {
final int index = i;
new Thread(new Runnable() {
public void run() {
System.out.println("Start client "+index);
String hostname = "127.0.0.1";
int port = 9001;
try {
SocketChannel scChannel = SocketChannel.open();
scChannel.connect(new InetSocketAddress(hostname, port));
ByteBuffer buffer = ByteBuffer.allocate(1024);
scChannel.read(buffer);
} catch (IOException e) {
e.printStackTrace();
System.exit(-1);
}
}
}).start();
}
2. 通过java程序运行ulimit -n时, 不能直接使用ulimit -n, 必须加上sh -c,如下所示:
$ sh -c 'ulimit -n'
1024
否则会报错:
java.io.IOException: Cannot run program "ulimit": error=2, No such file or directory
at java.lang.ProcessBuilder.start(ProcessBuilder.java:1041)
at java.lang.Runtime.exec(Runtime.java:617)
at java.lang.Runtime.exec(Runtime.java:485)
解决方法来自网站:
https://issues.apache.org/jira/browse/BLUR-134