【问题标题】:Memory Consumption by Java AppletJava Applet 的内存消耗
【发布时间】:2018-10-29 11:39:55
【问题描述】:

在我的小程序中,我有 GET 调用从远程位置下载文件。当我尝试下载一些 13MB 左右的大文件时,我的 Applet 内存消耗增加了 50MB 以上。我正在使用下面的代码来获取我的内存消耗:

public static long getMemoryUsage()
{
    long memory = 0;
    // Get the Java runtime
    Runtime runtime = Runtime.getRuntime();
    memory = runtime.totalMemory() - runtime.freeMemory();
    return memory;
}

我的 get 调用代码是

public  void getFiles(String filePath, long fileSize)throws MyException
    {
        InputStream objInputStream = null;
        HttpURLConnection conn = null;
        BufferedReader br = null;
        try 
        {
            URL fileUrl=new URL(filePath);
            final String strAPICall=fileUrl.getPath();
            final String strHost="some.test.com";
            final int iPort=1000;
            URL url = null;
            url = new java.net.URL
                        ( "https",
                                strHost, iPort , "/" + strAPICall,
                                new myHandler() );  

            conn = (HttpURLConnection)new HttpsURLConn(url);
            conn.setRequestMethod("GET");

            conn.connect();

            if (conn.getResponseCode() != 200) {

                objInputStream=conn.getInputStream();

                br = new BufferedReader(new InputStreamReader(
                (objInputStream)));

                String output;
                while ((output = br.readLine()) != null) {
                    System.out.println(output);

                }
                throw new MyException("Bad response from server", 
                            MyError.BAD_RESPONSE_ERROR);

            }
            else
            {

                notifyProgressToObservers(0);
                System.out.println("conn.getResponseCode()"+conn.getResponseCode());
                System.out.println("conn.getResponseMessage()"+conn.getResponseMessage());
                objInputStream  = conn.getInputStream();
                int count=objInputStream.available();

                System.out.println("Stream size: "+count);
                System.out.println("fileSize size: "+fileSize);
                byte []downloadedData = getBytesFromInputStream
                        (objInputStream, count,fileSize);
                notifyChunkToObservers(downloadedData);
                notifyIndivisualFileEndToObservers(true, null);

            }

        }
        catch (MyException pm)
        {
            throw new MyException
            (pm, MyError.CONNECTION_TIMEOUT);
        }
        catch (IOException pm)
        {
            throw new MyException
            (pm, MyError.CONNECTION_TIMEOUT);
        }
        catch (Exception e) 
        {

            notifyIndivisualFileEndToObservers(false,new MyException(e.toString()));
        }
        finally
        {
            System.out.println("Closing all the streams after getting file");
            if(conn !=null)
            {
                try
                {
                    conn.disconnect();
                }
                catch(Exception e)
                {

                }
            }
            if(objInputStream != null)
            {
                try {
                    objInputStream.close();
                } catch (IOException e) {

                }
            }
            if (br != null) 
            {
                try {
                    br.close();
                } catch (IOException e) {

                }
            }
        }

    }

在上面的方法中,我尝试将内存消耗的日志放在每一行之后,发现conn.connect();之后,即使我尝试下载的文件只有13MB,applet的内存消耗至少增加了50MB。

是否有内存泄漏?

编辑:添加了 getBytesFromInputStream() 的实现

public byte[] getBytesFromInputStream(InputStream is, int len, long fileSize)
            throws IOException 
    {
        byte[] readBytes= new byte[8192];
        ByteArrayOutputStream getBytes= new ByteArrayOutputStream();

        int numRead = 0;

        while ((numRead = is.read(readBytes)) != -1) {
            getBytes.write(readBytes, 0, numRead);
        } 


        return getBytes.toByteArray();
    }

【问题讨论】:

    标签: java


    【解决方案1】:

    因为这行:

     byte []downloadedData = getBytesFromInputStream(objInputStream, count,fileSize);
    

    在这里,您正在将文件的完整字节数读入堆中。之后,您需要跟踪这个数组发生了什么。也许您正在将它复制到某个地方,即使您不再使用对该对象的引用,GC 也需要一些时间来启动。

    永远不应将大文件完全读取到内存中,而应直接将其流式传输到数据的某个处理器。

    【讨论】:

    • 如果该方法使用 ByteArrayOutputStream.toByteArray,它会保留两次 13M(以及当内部缓冲区空间不足时分配的数组)。如果在 GC 运行之前加载数据就达到 50M,这不会感到惊讶。
    • 最多 3 次,因为 ByteArrayOutputStream 在需要增长时将其内部缓冲区的一侧翻倍。所以缓冲区可能会大 2 倍。然后考虑到由于重新分配导致的流失,这个过程很容易消耗超过 50GB 的 13GB 文件。
    • @StephenC,我添加了 getBytesFromInputStream() 的实现,你能建议我如何优化它以减少内存消耗。
    • 嗯,不错的理论,但实际上在某些情况下加载整个文件是不可避免的。
    【解决方案2】:

    优化getBytesFromInputStream() 的唯一方法是,如果您事先准确知道要读取多少字节。然后分配一个所需大小的byte[],并从输入直接读取到byte[]。例如:

      byte[] buffer = new byte[len];
      int pos = 0;
      while (pos < len) {
         int nosRead = is.read(buffer, pos, len - pos);
         if (nosRead == -1) {
             throw new IOException("incomplete response");
         }
         pos += nosRead;
      }
      return buffer;
    

    (欲了解更多信息,请阅读javadoc。)

    很遗憾,您(显然)尝试获取大小是不正确的。

      int count = objInputStream.available();
    

    这不会返回可以从流中读取的字节总数。它返回可以读取的字节数现在没有阻塞的可能性。

    如果服务器在响应中设置Content-Length 标头,那么您可以使用它;收到回复后,请致电 getContentLength()(或在其他用例中为 getContentLengthLong())。但是要为给你-1的情况做好准备。

    【讨论】:

    • 上述方案减少了几MB的内存消耗。例如,相同的 13MB 文件现在消耗大约 43MB。您是否看到任何其他可能的优化?我还是不明白为什么 13MB 的文件消耗了将近 3 倍的内存。
    • 您需要提供 MCVE。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-04-27
    • 2011-03-11
    • 2020-09-29
    • 1970-01-01
    • 1970-01-01
    • 2010-10-12
    相关资源
    最近更新 更多