【问题标题】:JVM doesn't release memory of byte array after thread ends working线程结束工作后JVM不释放字节数组的内存
【发布时间】:2015-03-29 10:50:46
【问题描述】:

我在 jvm 释放内存时遇到了问题。我知道java在退出run方法后释放线程资源的内存。当其他对象没有引用时,垃圾收集器会删除其他对象,但有一些例外,如窗口/框架。为什么在下面的代码中,尽管线程结束了工作,但 gc 不释放字节数组的内存?我知道 System.gc() 只是对 gc 的建议,但我使用它以防万一,为字节数组引用分配 null 是不必要的。
下面的代码只是示例,当服务器向客户端发送文件时,我的客户端-服务器应用程序在类似情况下遇到了真正的问题。

 private void jButton2ActionPerformed(java.awt.event.ActionEvent evt) {                                         
   int j=0;
    for (int i=0;i<5;i++){
       try {
           Thread.sleep(2000);
       } catch (InterruptedException ex) {
           Logger.getLogger(NewJFrame.class.getName()).log(Level.SEVERE, null, ex);
       }
       j++;
       new Thread(new Runnable(){
           public void run(){
               byte[] bytes=new byte[1024*1024*100];
               try {
                   Thread.sleep(15000);
               } catch (InterruptedException ex) {
                   Logger.getLogger(NewJFrame.class.getName()).log(Level.SEVERE, null, ex);
               }
               System.out.println("exiting "+Thread.currentThread().getName());
               bytes=null;
               System.gc();
           }
       }, ""+j).start();

   }
System.gc();     
} 

我离开上面的问题,去实用。早些时候,将整个文件加载到一个字节数组并使用 writeObject() 发送它,但这会导致内存问题。看看那个代码:

                BufferedOutputStream bos = null;                    
                byte[] bytes;
                int count;
                for (int i = 0; i < filesToUpdate.size(); i++) {                      
                    if (mapp.get(filesToUpdate.get(i)) == null) {
                        addNewFile(filesToUpdate.get(i));
                    }                        
                    bos = new BufferedOutputStream(new FileOutputStream(new File(filesToUpdate.get(i))));                        
                    long bufferSize = ois.readLong();                        
                    ous.writeObject(2);
                    ous.flush();                                                                             
                    bytes =new byte[8192];                       
                    while ((count=ois.read(bytes))>0){
                        bos.write(bytes, 0, count);                
                    }
                    bos.flush();
                    bos.close();                                                                  
                    ous.writeObject(3);
                    ous.flush();                        
                }
                ois.readObject();                    
                updateRevision(mapp, filesToUpdate);

接收文件的是客户端。在收到最后一个数据包后,第一个文件中的读取方法块。 这是服务器端:

       int count;           
        File file;
        FileInputStream fis=null;
        byte[] bytes;
        for (int i=0;i<filesForPatch.size();i++){
            if (pc.getWhat()==0)
                path="admin/";
            else path="client/";
            path+=filesForPatch.get(i);
            file=new File(path);                           
            long buffSize=file.length();
            ous.writeLong(buffSize);
            ous.flush();                                             
            ois.readObject();               
            fis=new FileInputStream(file);
            bytes=new byte[8192];
            while ((count=fis.read(bytes))>0){
                ous.write(bytes, 0, count);
            }
            ous.flush();
            fis.close();
            ois.readObject();             
        }    

任何想法如何解决这个问题?

【问题讨论】:

  • 如何检查 Java 没有释放字节数组的内存?基本上,你能发布一些东西来支持你的主张吗??
  • 我通过windows任务管理器查看。
  • 您能否详细说明 Windows 任务管理器的哪些统计信息让您相信导致内存泄漏的是字节数组,而不是程序的其他部分,或者您的其他应用程序整个系统?
  • 这是一个非常简单的程序,只有上面的代码和带有按钮创建的框架。那么还有什么可以解决内存泄漏呢?
  • 没错。所以 Windows 任务管理器不会告诉你它是字节数组。你觉得是字节数组是罪魁祸首吧?为什么感觉字节数组是罪魁祸首?

标签: java memory garbage-collection jvm


【解决方案1】:

您从 Windows 任务管理器获得的信息并不重要。 JVM 根据许多因素动态调整堆大小,吞吐量是首要考虑因素。堆大小永远不会完全等于可访问对象分配的实际内存。

如果您想观察垃圾回收的效果,请使用 VisualVM 连接到您的 JVM。为了获得最佳效果,安装 VisualGC 插件并打开它的选项卡,您将能够实时观察所有代的大小变化。垃圾收集将立即反映在显示的占用率中,您还可以注意到堆本身何时调整大小(很少)。

【讨论】:

  • 我如何在我的服务器-客户端应用程序中解决这个问题。我将文件加载到字节数组并将其发送给客户端。在发送 jvm 的每个文件期间,都会以大约文件大小的大小分配 ram,并且不会释放它。服务器在无限循环中工作,在一些客户端下载文件后,服​​务器的内存约为几 GB,应用程序不再抛出可用内存异常。
  • 这个描述听起来像是内存泄漏,这不是垃圾收集器的问题。您必须了解为什么阵列始终保持可访问性。但是顺便说一句,将整个文件复制到内存不是最佳方法,您应该将文件直接从磁盘到网络,然后从网络到服务器端的文件。在任何时间点,只有一个缓冲区的数据应该在 RAM 中。
  • 我首先像你说的那样做,但是在流接收到最后一个数据包后,我遇到了阻塞读取输入流的问题。 Available() 方法不能解决问题,因为它从我发送的下一个文件中获取数据并将其保存到上一个文件中。
  • 您可能遇到的任何问题都是由于 API 使用不正确,例如未正确关闭发送端的输出流。
  • 看我的第一篇文章(问题)。我粘贴了包含流问题的代码。
【解决方案2】:

for (int i=0;i

这很可能是 GC 没有足够的时间进入堆大小的稳定状态。如果您要运行 10000 次,您会发现它最终会稳定在一个稳定的内存量上。

所以您的示例不是您的客户端-服务器程序的简化测试用例。

因此,如果您的应用程序中存在实际内存,则无法根据该示例找到它。

查找泄漏的最简单方法是让程序运行一段时间,然后使用内存分析器(例如 yourkitjprofilereclipse's MAT)检查堆转储

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-08-05
    • 2014-06-14
    • 1970-01-01
    • 2012-01-25
    • 2010-10-26
    • 2019-09-08
    • 2011-09-13
    相关资源
    最近更新 更多