为什么Java不断吃更多内存?

时间:2011-02-25 20:59:11

标签: java memory

所以我有这个小客户端代码

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每2-3秒吃掉大约10kb的内存。因此,内存使用量不断增长和增长...

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) {       
}

7 个答案:

答案 0 :(得分: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

我们可以看到内存使用量有锯齿曲线 - 它是线性增加的,然后是垃圾收集,它正在降低到基本用法。在一些初始阶段之后,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个字符之间。由于StringBuilder的分配策略,后者(大约一半)实际上使用32-char-arrays,前者为16-char-arrays。这些需要64或32个字节+数组开销(至少12个字节,更可能更多)。此外,String对象本身需要一些内存开销(至少8 + 8 + 4 + 4 + 4 = 28用于类和我记忆的字段,更可能更多),因此我们平均每个字符串(至少)有88个字节。此外,ObjectOutputStream中可能存在一些开销,以便在某些数据结构中维护这些对象。

因此,实际上并不需要更多的损失。

啊,如果您不打算再次发送任何对象,如何避免存储对象的ObjectOutputStream(以及相应的ObjectInputStream)的一个提示:每次调用其reset方法几千串左右。

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

last screenshot

为了比较,我添加了名为reset并让程序再运行两个小时(以及一点点):

screenshot with reset

它仍然像以前一样收集内存,但现在当我点击“执行GC”时,它会清除所有内容并返回之前的状态(仅略高于1 MB)。 (在Heap结束时它也会这样做,但我不想等这么久。)

答案 1 :(得分:2)

当变量实际超出范围,或者你将大部分时间花在GC代码上时,垃圾收集器永远不会运行。

它的作用(这是一个非常简化)是它等待你的内存使用达到一个阈值,然后才开始释放内存。

这就是你所看到的,你的内存消耗增长如此之慢,以至于需要很长时间才能达到下一个阈值并实际释放内存。

答案 2 :(得分:2)

我不认为添加关闭是你的问题,因为我认为你想要做的就是继续写入流。你试过out.flush()吗?这会刷新内容,使其不再存在于内存中。

答案 3 :(得分:1)

看起来你永远不会关闭Socket或刷新ObjectOutputStream。另请注意,Java垃圾收集基本上不是在您需要它时,而是在垃圾收集器看起来合适时。

答案 4 :(得分:0)

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

答案 5 :(得分:0)

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

答案 6 :(得分:0)

ObjectOutputStream缓存了您发送的每个对象,以防您再次发送它。要清除此项,您需要调用reset()方法

  

重置将忽略已写入流的任何对象的状态。状态重置为与新的ObjectOutputStream相同。流中的当前点标记为重置,因此相应的ObjectInputStream将在同一点重置。先前写入流的对象将不会被称为流中的对象。它们将再次写入流中。

BTW:10 KB的内存价值约为0.1美分。你最低工资的一分钟时间是这个时间的100倍。我建议你考虑一下你最好的时间用途。