Java:泄漏的未知原因(非常简单的例子)

时间:2015-11-07 22:47:11

标签: java memory-leaks

我目前正在开发一款具有无限地形的游戏,所以我经常为每个块创建新的数组。我遇到的问题是,即使我删除了一个块的所有引用,它似乎没有释放它使用的内存。我用任务管理器检查了内存使用情况;但是,我也使用了VisualVM,它没有显示存在内存问题的任何迹象(分配的字节数保持一致)。无论如何,我设法将问题简化为这个简单的代码:

import java.awt.Dimension;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;

import javax.swing.JFrame;

public class Leaker
{
    public static void main(String[] args)
    {
        new Leaker().run();
    }

    public void run()
    {
        JFrame frame = new JFrame();
        frame.setPreferredSize(new Dimension(100, 100));
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);

        frame.addKeyListener(new KeyListener()
        {

            @Override
            public void keyPressed(KeyEvent e)
            {
                if (e.getKeyCode() == KeyEvent.VK_1)
                {
                    double[] leak = new double[9999999];
                    System.out.println("*leaking intensifies*");
                }
                if (e.getKeyCode() == KeyEvent.VK_2)
                {
                    System.gc();
                    System.out.println("garbage collected");
                }
            }

            @Override
            public void keyTyped(KeyEvent e)
            {

            }

            @Override
            public void keyReleased(KeyEvent e)
            {

            }

        });
    }
}

我认为应该发生的是当我按下1时,会分配一个新的双数组,但由于它只是在堆栈上分配,一旦该方法返回,内存将被释放不久之后。然而,这种情况并非如此。每次按1时,任务管理器都会显示此应用程序使用的内存量越来越大。即使强制垃圾回收也没有效果。那么,我在这里错过了什么?

如果由于某种原因你不相信我,只需复制并粘贴即可。

5 个答案:

答案 0 :(得分:2)

  

我认为应该发生的是,当我按下1时,将分配一个新的双数组,但由于它只被分配在堆栈上,一旦该方法返回,内存将很快被释放。

这是不正确的。

double数组在堆上分配。所有对象都在堆 1 上分配。

对将在堆栈中的局部变量中保存的对象的引用。到keyPressed()方法返回时,本地将消失,使双数组无法访问。但是直到GC解决它才真正被收回。

在正常情况下,JVM仅在检测到堆空间不足时才运行GC。 (实际的触发条件取决于您使用的GC类型...)您试图通过调用System.gc()来强制解决问题,在您的情况下 2 这个触发GC。

所以... GC运行,并回收空间。你怎么没有看到这个?好吧,你不会说你如何监视JVM的内存使用情况,但我希望你使用的是操作系统级工具(例如top或windows任务监视器)。这些工具赢了&# 39; t 当GC回收空间时,看到进程内存使用量下降。那是因为GC通常不会回复"回收的操作系统空间。相反,它为下次应用程序分配大对象/数组时保留了空间。 (这使Java内存管理更简单,更高效。)

JVM可以配置为通过(又一个)JVM切换回馈不需要的内存,但即便是这种机制也不情愿。在JVM决定它有更多堆内存而不是需要它之前需要几个GC周期。

总结:你在这里所拥有的不是"内存泄漏"。相反,有证据表明Java不会立即回收空间,并且它不愿意回馈"任何回收的操作系统堆空间。它肯定不会立刻回复记忆。

1 - 有一个JVM开关可以启用称为"逃逸分析"在JIT编译器中。这将导致它尝试识别分配了一个不能逃脱的对象的情况。本地方法调用,因此可以在堆栈上分配。但是,我不认为它适用于数组,它肯定不适用于这个数组......因为数组太大而无法在线程堆栈上分配。

2 - 您将看到调用System.gc()的所有地方的引用都不能保证GC运行。这是真的...... javadocs这样说。但是,在实践中 System.gc()将运行GC,除非JVM启动时带有标记,表示"忽略System.gc()"呼叫。

答案 1 :(得分:1)

@TizianoPiccardi是对的。如果在程序中执行-Xmx128M作为命令参数,则使用的内存量不应超过128MB限制,并且当总内存达到该限制时,将导致未使用的内存被清除。如果您使用eclipse,请在“运行配置”中键入参数 - >参数 - > VM参数

答案 2 :(得分:0)

数组是一个对象,所有对象都在堆上分配。您创建的数组的本地引用位于堆栈中。

System.gc()只是建议Java VM应该运行垃圾收集,但不保证能够运行。

您可以使用Visual GC的{​​{1}}插件来查看gc何时发生。对我来说,当我按下你的2个代码时(或当我按下1直到Eden空间已满)时,Eden空间被清理干净了。

总之,我认为这里没有泄漏。

答案 3 :(得分:0)

所以,我的泄漏问题根本不是一个漏洞问题。为了测试Stephen C解释的内容,我使用了Streak324和TizianoPiccardi的建议,即当堆中的内存实际通过-XMX256M返回给操作系统时设置堆内存限制。我的游戏现在永远不会超过这个限制。事实上,如果我刚刚运行了足够长的游戏,它将永远不会超过1024MB,但我从未尝试过。无论如何,谢谢大家。

答案 4 :(得分:0)

你在哪里写-XMX256M? 是否有可以使此设置应用程序具体的位置? 或者你只是在一般的java设置中写它,因为这个限制用于你正在运行的任何java应用程序。