【问题标题】:Tomcat Performance with Huge String Responses具有巨大字符串响应的 Tomcat 性能
【发布时间】:2026-02-06 10:45:01
【问题描述】:

我有一个 springRest Web 服务端点,它返回一个大小为 4MB 的字符串文本。当我们对此端点进行负载测试时,我们经常看到堆峰值并最终导致系统崩溃。我在想——当我们提出请求时,每个请求都由一个线程单独服务。我的假设是:因为字符串保存在全局静态变量中,所以每个线程获取 4MB 的副本,并且在大约 3000 个请求之后,堆全部被消耗并且系统崩溃,因为每个 4MB 的 3000 个线程大约是 12GB,因此系统内存不足。但这是我的假设。

我的问题:在处理请求的每个线程完成它的工作之后,tomcat 不会回收内存吗? 这与GC(垃圾收集)有关吗? 在请求生命周期中 - 随着请求的到来,创建了一个线程(根据该请求)该线程是获取它自己的响应副本还是仅引用响应?如果将那个巨大的字符串响应复制到每个线程,那么这可能就是显示堆峰值的原因。 当响应返回给客户端时,tomcat 如何回收该线程的资源?它什么时候做?是否在声明与 GC 相关的请求线程?

我观察到的另一个方面是:socketWrite0() 方法的延迟 - 这需要 70-95% 的响应时间。我认为这是一个瓶颈。那么在请求响应的流程中——谁写入套接字?线程?还是线程将响应交给 tomcat 并由 tomcat 写入?

如果你们中的任何人可以给我一个提示或一个方面来看看将内存峰值与巨大的字符串响应联系起来,我真的很感激。谢谢大家!

玫瑰

【问题讨论】:

    标签: java performance spring rest scalability


    【解决方案1】:

    如果您声明了一个静态最终字符串,则该变量应在线程之间共享。如果没有任何代码,几乎不可能说出发生了什么。

    您是否尝试过使用像 JProfilermat 这样的分析工具?

    【讨论】:

    • 谢谢蒂姆。我正在使用 VisualVM 进行分析。我们还使用 dynatrace 来进行负载/性能测试。我正在阅读 dynatrace 报告中的内存峰值。
    • 我似乎记得 VisualVM 允许您检查堆。所以在高峰期你应该会在内存中看到很多大的字符串对象。
    【解决方案2】:

    我假设你所做的是

    response.getWriter().write(hugeString);
    

    如果这导致问题,tomcat 是罪魁祸首,它可能没想到这么大的字符串。

    你可以试着把字符串剪成更小的字符串,每次只写一个小字符串,例如

    int N = hugeString.length();
    int CHUNK = 8*1024;
    for(int i=0; i<N; i+=CHUNK)
        int end = Math.min(i+CHUNK, N);
        writer.write( hugeString.substring(i, end) );
    

    【讨论】:

      【解决方案3】:

      感谢大家提供的 cmets。 这个问题非常神秘,因为它只有在系统受到多个用户的性能测试压力时才会出现。我们尝试对缓存客户端、缓存策略和缓存生存时间、连接超时进行不同的更改。

      我们尝试更改算法。缓存更多数据以避免昂贵的旧系统调用。

      我们尝试将数据结构从 JSONArray 更改为 String。

      都没有帮助!!

      最后我们发现是负载均衡器造成了问题。我们禁用了它,一切正常。

      如果我有机会说一件事:企业系统的性能/可扩展性受到性能较差的组件的限制,因此请进行仔细的经验测试并在花时间找出正确的痛点后解决问题.不要跑来更改您的代码。

      【讨论】: