Last time I have promised you to take a look at more real life scenario regarding threads. In the last blog entry I have shown that on modern operating system and JVM it's not a problem to create 32,000 threads. Now I want to test how many threads can be handled by a Tomcat instance.

I just want to remind you the motivation. Some people believe that threads are expensive, that we should not create lot of them. They believe that it's better to use different mechanisms like asynchronous servlets, specialized libraries etc. I just want to find out if we really need such measures or if good old threads are good enough.

If you read articles about asynchronous servlets, you find out that the main motivation is AJAX. Mainly the scenario, when a HTTP connection is open for a long time and the data are sent when an event occurs.

OK, let's simulate it. We need to simulate lot of open HTTP connections waiting for an event. The easiest way to achieve it is my precious suicidal servlet.

  1. public class ThreadsServlet extends HttpServlet {
  2. private static final long serialVersionUID = 7770323867448369047L;
  3. @Override
  4. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  5. int number = Integer.valueOf(req.getParameter("number"));
  6. try {
  7. System.out.println("Servlet no. "+number+" called.");
  8. URL url = new URL(req.getScheme()+"://"+req.getServerName()+":"+req.getServerPort()+req.getRequestURI()+"?number="+(number+1));
  9. Object content = url.getContent();
  10. resp.setContentType("plain/text");
  11. resp.getWriter().write("OK: "+content);
  12. } catch (Throwable e) {
  13. String message = "Reached "+number+" of connections";
  14. System.out.println(message);
  15. System.out.println(e);
  16. resp.getWriter().write(message);
  17. }
  18. }
  19. }

The servlet is quite simple, it just opens HTTP connection to itself. So it basically tries to create infinite number of connections. Top keep track of the progress, there is a request parameter “number” that is incremented with each call. We can thus observe how many active connections we have.

Default configuration

Let's run it. Just open “http://localhost:8080/threads/something?number=1” in your browser and see what happens.

Not much, in console (or logs/catalina.out) you can see

...
Servlet no. 37 called.
Servlet no. 38 called.
Servlet no. 39 called.
Servlet no. 40 called.

What? Only 40 concurrent threads served? That's not much. Let's try better.

Connector configuration

We can reconfigure Tomcat connector to be able to serve more connections (server.xml)

  1. <Connector port="8080" protocol="HTTP/1.1"
  2. connectionTimeout="20000"
  3. redirectPort="8443"
  4. maxThreads="32000"/>

As we know from the last time, 32K is the OS limit, we can't go over that. If we execute the test, the results are slightly better:

Servlet no. 485 called.
Servlet no. 486 called.
Servlet no. 487 called.
Servlet no. 488 called.
Servlet no. 489 called.
Servlet no. 490 called.
May 1, 2010 5:55:32 PM org.apache.tomcat.util.net.JIoEndpoint$Acceptor run
SEVERE: Socket accept failed
java.net.SocketException: Too many open files
at java.net.PlainSocketImpl.socketAccept(Native Method)
at java.net.AbstractPlainSocketImpl.accept(AbstractPlainSocketImpl.java:358)
at java.net.ServerSocket.implAccept(ServerSocket.java:470)
at java.net.ServerSocket.accept(ServerSocket.java:438)
at org.apache.tomcat.util.net.DefaultServerSocketFactory.acceptSocket(DefaultServerSocketFactory.java:61)
at org.apache.tomcat.util.net.JIoEndpoint$Acceptor.run(JIoEndpoint.java:310)
at java.lang.Thread.run(Thread.java:636)

Wow, it looks like, that there is some limit on open files. Since I am not Linux guru, the first thing I have tried was to change Tomcat connector to nonblocking.

Nonblocking Connector

To use nonblocking connector, you have to set the protocol in server.xml

  1. <Connector port="8080"
  2. connectionTimeout="20000"
  3. redirectPort="8443"
  4. protocol="org.apache.coyote.http11.Http11NioProtocol"
  5. maxThreads="32000"/>

Unfortunately the result is almost the same:

Servlet no. 483 called.
Servlet no. 484 called.
Servlet no. 485 called.
Servlet no. 486 called.
May 1, 2010 5:59:24 PM org.apache.tomcat.util.net.NioEndpoint$Acceptor run
SEVERE: Socket accept failed
java.io.IOException: Too many open files
at sun.nio.ch.ServerSocketChannelImpl.accept0(Native Method)
at sun.nio.ch.ServerSocketChannelImpl.accept(ServerSocketChannelImpl.java:163)
at org.apache.tomcat.util.net.NioEndpoint$Acceptor.run(NioEndpoint.java:1198)
at java.lang.Thread.run(Thread.java:636)

Increase Open File Limit

Apparently, the blocking connector was not the issue. After some time spent with Google I have found the answer. Linux is limiting number of open connections. You can execute “ulimit -n” to see what's your limit. Luckily, it's possible to change the limit. You can either set it by “ulimit -n 32768” if you have permissions or by adding following lines to /etc/security/limits.conf (lukas is my username)

lukas            hard    nofile          32768
lukas soft nofile 32768

To apply this change you have to logout and login. After that, you will see this:

Servlet no. 5856 called.
Servlet no. 5857 called.
Servlet no. 5858 called.
Servlet no. 5859 called.
May 1, 2010 6:07:58 PM org.apache.tomcat.util.net.NioEndpoint$SocketProcessor run
SEVERE:
java.lang.OutOfMemoryError: GC overhead limit exceeded
at java.util.Arrays.copyOf(Arrays.java:2894)
at java.lang.AbstractStringBuilder.expandCapacity(AbstractStringBuilder.java:117)
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:407)
at java.lang.StringBuilder.append(StringBuilder.java:136)
at java.lang.StringBuilder.append(StringBuilder.java:132)
at java.lang.Throwable.printStackTrace(Throwable.java:529)
at java.util.logging.SimpleFormatter.format(SimpleFormatter.java:94)
at java.util.logging.StreamHandler.publish(StreamHandler.java:196)
at java.util.logging.ConsoleHandler.publish(ConsoleHandler.java:105)
at java.util.logging.Logger.log(Logger.java:476)
at java.util.logging.Logger.doLog(Logger.java:498)
at java.util.logging.Logger.logp(Logger.java:698)
at org.apache.juli.logging.DirectJDKLog.log(DirectJDKLog.java:167)
at org.apache.juli.logging.DirectJDKLog.error(DirectJDKLog.java:135)
at org.apache.coyote.http11.Http11NioProtocol$Http11ConnectionHandler.process(Http11NioProtocol.java:755)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:2080)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1110)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603)
at java.lang.Thread.run(Thread.java:636)

We have reached 5856 threads before we run out of memory. Cool.

Increase Heap Size

Let's try to increase the heap size. Just add “-Xmx2048m” to JAVA_OPTS. Before I have started running out of heap, I got to cca 11000 threads! Is it enough? I do not know, but I think it's pretty good.

Moreover, if you do a heap dump, you will see, that most of the memory is consumed by char and byte arrays. (This heap dump has been taken with approximately 5000 connections, screenshot from VisualVM)

It's understandable, we have lot of open buffers on both sides. I assume that we have at least four buffers per servlet. One for sevlet request, one for servlet response, one for URL request and one for URL response. But maybe there will be other buffers as well. To be honest, I have to admit that memory consumed by stacks would not appear here, it's probably handled by OS. But we have run out of the heap, so that's why I am talking about it.

Smaller buffers

We can try to make some of the buffers smaller. I was able to find only one setting that had some effect. Again it is connector setting in server.xml config file.

  1. <Connector port="8080"
  2. connectionTimeout="200000"
  3. redirectPort="8443"
  4. protocol="org.apache.coyote.http11.Http11NioProtocol"
  5. maxThreads="32000"
  6. socket.appReadBufSize="1024"
  7. socket.appWriteBufSize="1024"
  8. bufferSize="1024"/>

With this setting, I was able to get near to 13000 open connections.

...
Servlet no. 13327 called.
Servlet no. 13328 called.
Servlet no. 13329 called.
Servlet no. 13330 called.
Servlet no. 13331 called.
Servlet no. 13332 called.

After that the machine started to run out of physical memory, GC took ages so I had to stop the server. (Just to remind you, my test machine is two year old laptop with Intel Core 2 Duo T8100 2.1GHz with 4GB of RAM. There is 64bit Linux 2.6.32 and OpenJDK (IcedTea6 1.8) running on top of it.)

As we have seen, threads are not the major issue on modern machines. There is probably significant amout of memory consumed by the stack traces too, but I think the biggest problem are the buffers. And the important point is, that we would need the buffers even if we used asynchronous servlets! Of course, there is still some overhead connected with threads, so asynchronous libraries have their place. In fact, it would be nice to try similar experiment with asynchronous servlets. I am afraid, that I will not be able to do it, but I will be glad to help if there is some volunteer.

Please also note that your numbers may vary. After all this has been quite artificial test. I think it's simulates lot of real-life use cases, but you know, the reality is always different.

On the other hand, with more physical memory and better Tomcat configuration, we might got to higher numbers. I have heard legends about 16K threads.

I think that I will finish with my favorite message. Do not use complicated constructs unless you are sure you need them. Please remember golden rules of optimization:

The First Rule of Program Optimization: Don't do it.
The Second Rule of Program Optimization (for experts only!): Don't do it yet.

If you want to verify my results, the source code is here. If you have some comments, different results or advices, do not hesitate to add a comment.

Resources:
Tomcat connector config

Why we need asynchronous servlets

Note: If you wonder why the hell I have started to write in
something that looks almost like English when apparently I do even more
mistakes than in Czech, the answer is simple. I just need to practice my
English (apart from that I want to be world famous, not just known in
Czech Republic)

[转载]Cool, Tomcat is able to handle more than 13,000 concurrent connections的更多相关文章

  1. Using the FutureRequestExecutionService Based on classic (blocking) I/O handle a great number of concurrent connections is more important than performance in terms of a raw data throughput

    Chapter 7. Advanced topics http://hc.apache.org/httpcomponents-client-ga/tutorial/html/advanced.html ...

  2. handle exceptions, opening and closing database connections

    https://www.tutorialspoint.com/spring/spring_jdbc_framework.htm Spring - JDBC Framework Overview Whi ...

  3. Could not create cudnn handle: CUDNN_STATUS_INTERNAL_ERROR tensorflow-1.13.1和1.14windows版本目前不支持CUDA10.0

    报错出现 Could not create cudnn handle: CUDNN_STATUS_INTERNAL_ERROR tensorflow-1.13.1和1.14windows版本目前不支持 ...

  4. 【转载】Tomcat崩溃事件

    转载地址:http://www.blogjava.net/tedeyang/archive/2008/06/04/205740.html Tomcat崩溃事件 今天一大早产品一部项目经理就来找我,他们 ...

  5. 转载:Tomcat多数据源配置方法

    转载网址:http://blog.sina.com.cn/s/blog_53803b7b010144u5.html 关于在TOMCAT下配置多数据源,网上有很多方式,但是感觉也很混乱,俺只说俺们使用的 ...

  6. [转载]启动tomcat时,一直卡在Deploying web application directory这块的解决方案

    转载:https://www.cnblogs.com/mycifeng/p/6972446.html 本来今天正常往服务器上扔一个tomcat 部署一个项目的, 最后再启动tomcat 的时候 发现项 ...

  7. 【转载】tomcat部署web项目的3中方法

    转载自:http://blog.csdn.net/wjx85840948/article/details/6749964/ 1.直接把项目复制到Tomcat安装目录的webapps目录中,这是最简单的 ...

  8. (转载)Tomcat 7集群浅析

    本文转载自:http://blog.csdn.net/wangyangzhizhou. 如有侵权,请联系处理!   简介 每个节点都要维护一份集群节点信息列表,集群组通知的默认实现是在使用 UDP 数 ...

  9. 【转载】tomcat+nginx+redis实现均衡负载、session共享(一)

    http://www.cnblogs.com/zhrxidian/p/5432886.html 在项目运营时,我们都会遇到一个问题,项目需要更新时,我们可能需先暂时关闭下服务器来更新.但这可能会出现一 ...

随机推荐

  1. 开源视频会议bigbluebutton

    这是另一个开源视频会议项目,简称bbb 官方网站:http://bigbluebutton.org/ 代码地址:https://code.google.com/p/bigbluebutton/ dem ...

  2. 软件魔方制作系统启动盘并安装win7系统

    不多说,直接上干货! 推荐软件:软件魔方 http://mofang.ruanmei.com/ 这里,我想说的是,这个软件来制作系统盘,是真的方便和好处多多.具体我不多说,本人也是用过其他的如大白菜等 ...

  3. JavaScript位移运算多个大于号的使用方法

    JavaScript中的无符号位移运算符是用三个大于号来表示的 计算方法 例 100>>>2 100的二进制是 01100100 向右移2位后为 00011001 最后结果为25 1 ...

  4. 前端通信:ajax设计方案(四)--- 集成ajax上传技术 大文件/超大文件前端切割上传,后端进行重组

    马上要过年了,哎,回家的心情也特别的激烈.有钱没钱,回家过年,家永远是舔舐伤口最好的地方.新的一年继续加油努力. 上次做了前端的ajax的上传文件技术,支持单文件,多文件上传,并对文件的格式和大小进行 ...

  5. CentOS7安装SaltStack的踩坑经历

    虚拟机镜像:centOS7 minion 版本 软件版本: salt-master-2017.7.2-1.el7.noarchsalt-minion-2017.7.2-1.el7.noarchsalt ...

  6. Vue前端框架面试问题

    1.active-class是哪个组件的属性?嵌套路由怎么定义? 答:vue-router模块的router-link组件. 2.怎么定义vue-router的动态路由?怎么获取传过来的动态参数? 答 ...

  7. Pipenv——最好用的python虚拟环境和包管理工具

    pipenv 是Kenneth Reitz大神的作品,能够有效管理Python多个环境,各种包.过去我们一般用virtualenv搭建虚拟环境,管理python版本,但是跨平台的使用不太一致,且有时候 ...

  8. ruby中Regexp用法

    Regexp 正则表达式的类.正则表达式的字面值是以双斜线内夹表达式的形式生成的. /^this is regexp/ 还可以使用Regexp.new(string)来动态地生成正则表达式对象. 超类 ...

  9. Firebird 手动安装 Legacy_Auth 登陆认证

    下载官方发布的最新版本:V3.0.4 或者下载还没正式发布的下个版本(但已经修复了一些bug):V3.0.5 下载后解压缩,修改配置文件Firebird.conf的登陆认证为Legacy_Auth:这 ...

  10. Autofac--手动依赖注入

    本文只适合初步接触 autoafc 的小白,大佬勿喷. autofac是一种很轻量的一个依赖注入容器 暂时先说一下手动注入的方式(因为使用自动注入遇到了坑,暂时先写一下手动注入) 1.先定义接口 pu ...