码头延续和无法解释的记忆使用模式

时间:2012-12-01 23:16:36

标签: memory-leaks jetty continuations

我一直在努力寻找看起来非常基本的东西,这个问题与使用Jetty延续进行长期调查有关。

为了简单起见,我删除了所有特定于应用程序的代码,只留下了简单的延续相关代码。

我正在粘贴下面的servlet的doPost方法。我需要一些专家指导的关键问题是

  • 在下面的代码块中,如果我按原样运行它并且发送带有大约200字节的帖子主体的请求,则500个长轮询连接的内存量大约为20 MB。
  • 如果我将块突出显示为“减少内存占用::下面的注释块”,那么内存占用量将降至7 MB

在两种情况下我等待系统稳定,多次调用GC然后通过jConsole读取内存。它并不精确,但差异是如此之多和可解释的,这里或那里的精度只有几百字节无关紧要。

我的问题爆炸了,考虑到我的服务器需要保持100K连接,如果不是更多。在这里,这种无法计划的大小增加最终导致接近使用额外堆的GB。

(什么导致这个额外的堆使用,甚至从流中读取的内容都不会保留在doPost方法的范围之外。但它仍然会添加到堆中......我缺少什么?)

   @Override
   protected void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {

    Continuation cc = ContinuationSupport.getContinuation(req);

    //if continuation is resumed, then send an answer back with 
    //hardcoded answer
    if (cc.isResumed()) {
        String myJson = "{\"1\",\"2\"}";
        res.setContentType("application/json");
        res.setContentLength(myJson.length());
        PrintWriter writer = res.getWriter();
        writer.write(myJson);
        writer.close();
    } 
    // if it is the first call to doPost ( not reentrant call )
    else if (cc.isInitial()) {          

        //START :: decrease memory footprint :: comment this block :: START

        // store the json from the request body in a string
        StringBuffer jsonString = new StringBuffer();
        String line = null;                      
        BufferedReader bufferedReader = req.getReader();
        while ((line = bufferedReader.readLine()) != null) {
            jsonString.append(line);
        }  

        //here jsonString was parsed and some values extracted
        //though that code is removed for the sake of this publish
        // as problem exists irrespective...of any processing

        line = null;            
        bufferedReader.close();
        bufferedReader = null;
        jsonString = null;

        // END :: decrease memory footprint :: comment this block :: END

        cc.setTimeout(150000);        

        cc.suspend();
    }
}

1 个答案:

答案 0 :(得分:1)

  

造成这种额外堆使用的原因......

看看这一行:

BufferedReader bufferedReader = req.getReader();

请注意,您实际上并未创建新的BufferedReader。当您调用getBufferedReader时,Jetty会创建一个BufferedReader,它包装一个InputStreamReader,它包装一个包装字节缓冲区的自定义InputStream实现。我很确定通过执行读取整个消息的代码,您可以在请求对象中创建大字节缓冲区,该缓冲区存储消息体的全部内容。此外,请求对象维护对读者的引用。

在您调用的函数的开头:

Continuation cc = ContinuationSupport.getContinuation(req);

我相信你的继续保留了存储所有数据的请求。因此,读取数据的简单行为是分配将保留的内存,直到您继续discontinue为止。

有些事情你可以尝试作为一个实验。将您的代码更改为:

BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(req.getInputStream()));

这样Jetty就不会分配它自己的读者。再说一遍 - 我不知道与其他请求对象相比,读者中存储了多少数据 - 但它可能会有所帮助。

[更新]

另一种选择是避免这个问题。这就是我所做的(尽管我使用的是servlet 3.0而不是Continuations)。我有一个资源 - 让我们称之为/transfer,这将POST一些数据,然后使用AsyncContext等待响应。我将其更改为两个具有不同网址的请求 - /push/pull。每当我有一些需要从客户端发送到服务器的内容时​​,它会进入/push请求,然后在不创建AsyncContext的情况下立即返回。因此,请求中的任何存储都会立即释放。然后等待响应,我发送了第二个GET请求,没有消息正文。当然 - 请求暂停了一段时间 - 但是谁在乎 - 它没有任何内容。

您可能需要重新考虑您的问题,并确定您是否可以分段执行任务 - 多个请求 - 或者您是否真的必须在单个请求中处理所有内容。