使用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.csdn.net/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