2011-02-25 19 views
1

所以我有这个小客户端代码为什么Java不断地吃更多的内存?

public class Client { 

    private static Socket socket; 
    private static ObjectOutputStream out; 

    public static void main(String[] args) { 
     while (true) { 
      try { 
       if (socket != null) { 
        out.writeObject("Hello..."); 
        Thread.sleep(1500); 
       } else { 
        socket = new Socket("myhost", 1234); 
        out = new ObjectOutputStream(socket.getOutputStream()); 
        System.out.println("connected to server"); 
       } 
      } catch (final Exception e) { 
         //set socket to null for reconnecting 
      } 
     } 
    } 

} 

我有什么错误,当我运行的javaw.exe的代码,我看到,Java是吃〜10KB的内存,每2-3秒。所以内存使用量不断增长,并不断增长...

是java真的很糟糕还是有其他问题?


我在while循环中运行了一段代码,内存使用增加了1000 kb。 java gargabe在使用后不会收集'tmp'变量吗?

try { 
    if (socket == null) { 
     final Socket tmp = new Socket("localhost", 1234); 
     if (tmp != null) { 
      socket = tmp; 
     } 
     Thread.sleep(100); 
    } 
} catch (final Exception e) {  
} 
+2

在强制gc后会发生什么?请记住,内存可能会以10kb的增量进行分配,但除非实际发生泄漏,否则它将全部收集起来,然后在分配新内存之前将其恢复为原始使用(或大约)。 – 2011-02-25 21:01:10

+0

如果你让它运行足够长时间,你会得到一个OutOfMemoryError? – Poindexter 2011-02-25 21:02:14

+5

你是不是应该关闭socket? – Alexandru 2011-02-25 21:04:17

回答

2

那么当一个变量实际上超出了范围,或者你会花大部分时间在GC代码中时,垃圾回收器从不运行。

它做了什么而不是(这是一个相当简化)是它等待,直到你的内存使用达到了一个门槛,然后才开始释放内存。

这就是你所看到的,你的内存消耗增长非常缓慢,需要很长时间才能达到下一个门槛并实际释放内存。

+0

这也是一个有效的点 – 2011-02-25 21:13:42

0

IO由Socket实现缓存直到刷新。所以要么你真的从套接字读取输入/输出(或者在流上调用#flush()),要么关闭套接字。

1

它看起来像你永远不会关闭套接字或刷新ObjectOutputStream。还要注意,Java垃圾收集基本上不会在你想要的时候发生,但是当垃圾收集器看起来合适时。

2

我不认为增加关闭是你的问题,因为我认为你要做的就是不断写入流。你有没有试过out.flush()。这会刷新内容,使其不在内存中。

0

对我来说,逻辑本身就是罪魁祸首,没有条件从while循环中走出来。 再次没有冲水。

8

所以,我已经为您的客户端编写了一个简单的测试服务器,现在我正在运行这两个测试服务器,并且似乎没有增加内存使用量。

import java.net.*; 
import java.io.*; 


/** 
* example class adapted from 
* http://stackoverflow.com/questions/5122569/why-is-java-constantly-eating-more-memory 
*/ 
public class Client { 

    private static Socket socket; 
    private static ObjectOutputStream out; 

    private static void runClient() { 
     while (true) { 
      try { 
       if (socket != null) { 
        out.writeObject("Hello..."); 
        Thread.sleep(100); 
        System.out.print(","); 
       } else { 
        socket = new Socket("localhost", 1234); 
        out = new ObjectOutputStream(socket.getOutputStream()); 
        System.out.println("connected to server"); 
       } 
      } catch (final Exception e) { 
         //set socket to null for reconnecting 
       e.printStackTrace(); 
       return; 
      } 
     } 
    } 

    private static void runServer() throws IOException{ 
     ServerSocket ss = new ServerSocket(1234); 
     Socket s = ss.accept(); 
     InputStream in = s.getInputStream(); 
     byte[] buffer = new byte[500]; 
     while(in.read(buffer) > 0) { 
      System.out.print("."); 
     } 
    } 


    public static void main(String[] args) 
     throws IOException 
    { 
     if(args.length > 0) { 
      runServer(); 
     } 
     else { 
      runClient(); 
     } 
    } 

} 

你在做什么不同?

因此,我对该程序的内存使用情况进行了更详细的调查,发现这是一个有用的工具,隐藏在我的系统的开发菜单中的“Java监视和管理控制台”:-)

这里是内存使用的截图运行客户端程序一段时间(每次100毫秒我派遣一个对象,记不清了),而...

screenshot

我们可以看到,内存使用有看到了牙齿曲线 - 它逐渐增加,然后出现垃圾收集,并且正在跌落到基本usag即经过一段初始阶段后,VM更频繁地(并因此更快)执行GC。现在,没问题。


这里是一个变种节目里我并没有总是发送相同的字符串,但每次一个不同:

private static void runClient() { 
    int i = 0; 
    while (true) { 
     try { 
      i++; 
      if (socket != null) { 
       out.writeObject("Hello " + i + " ..."); 
       Thread.sleep(100); 
       System.out.print(","); 

(剩下的就是像上面)。我认为这需要更多的内存,因为ObjectOutputStream必须记住哪些对象已经发送,以便能够重用它们的标识符以防再次出现。

但是,没有,它看起来非常相似:

another screenshot

39和40之间的小不规则性是由“执行GC”按钮,在这里做手动完整GC - 它并没有太大变化,但。


我让最后一个程序运行的时间长一点,现在我们看到的ObjectOutputStream仍然占据着我们的字符串引用...

third screenshot, later

在半小时我们的节目吃大约2 MB的内存(在64位VM上)。在这个时候,它发送了18000个字符串。所以,每个字符串平均使用大约100个字节的内存。

这些字符串中的每一个都在11到17个字符之间。后者(大约一半)实际上使用32字符数组,前者是16字符数组,因为StringBuilder的分配策略。这些需要64或32字节+数组开销(至少多12个字节,更可能更多)。此外,String对象本身需要一些内存开销(对于类和我记忆的字段,至少8 + 8 + 4 + 4 + 4 = 28,更可能更多),所以每个字符串平均具有(至少)88个字节。此外,在ObjectOutputStream中可能会有一些开销来维护这些对象的某些数据结构。

所以,没有比实际需要更多的损失。

啊,如何避免用ObjectOutputStream一个尖端(以及相应的ObjectInputStream的,太)存储的对象,如果你不发送任何人再次计划:调用它reset方法每天几千串左右。

这里是最后的截图之前,我杀程序,经过一个多小时了一下:

last screenshot

为了便于比较,我加入了一个名为reset,让程序运行两小时(和位):

screenshot with reset

它作为前仍会收集记忆,但现在当我点击“执行GC”可以清理一切,去BAC k在之前的状态(仅略高于1 MB)。 (在堆结束时它会这样做,但我不想等这么久。)

+0

如果有人想过测试,我添加了一个类unifiying所有这些版本我的[github上库(https://github.com/ePaul/stackoverflow-examples/blob/master/src /de/fencing_game/paul/examples/SerializationMemoryLeak.java)。 – 2011-03-04 16:20:28

+0

我......我被这个答案的纯粹质量所感动...... – slezica 2011-05-20 01:12:47

0

ObjectOutputStream缓存发送的每个对象,以备再次发送。要清除这一点,你需要调用reset()方法

重置将丢弃已写入流中的所有对象的状态。状态被重置为与新的ObjectOutputStream相同。流中的当前点标记为重置,因此相应的ObjectInputStream将在同一点重置。以前写入流的对象不会被认为已经在流中。他们会再次被写入流中。

顺便说一句:10 KB的价值约为0.1美分的内存。你最低工资的一分钟时间是这个价值的100倍。我建议你考虑什么是你最好的时间。