我目前正在开发一款具有无限地形的游戏,所以我经常为每个块创建新的数组。我遇到的问题是,即使我删除了一个块的所有引用,它似乎没有释放它使用的内存。我用任务管理器检查了内存使用情况;但是,我也使用了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时,任务管理器都会显示此应用程序使用的内存量越来越大。即使强制垃圾回收也没有效果。那么,我在这里错过了什么?
如果由于某种原因你不相信我,只需复制并粘贴即可。
答案 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应用程序。