linux系统中 JVM隐式修改打开文件数研究

使用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

你可能感兴趣的:(nio,ulimit)