简体   繁体   English

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

[英]Java: Unknown Cause of Leak (Very Simple Example)

I'm currently working on a game with infinite terrain, so I am frequently creating new arrays for each chunk. 我目前正在开发具有无限地形的游戏,因此我经常为每个块创建新的数组。 The problem I ran into was that even after I removed all references of a chunk, it appeared that the memory it used was not freed. 我遇到的问题是,即使我删除了一个块的所有引用,它似乎仍未释放所使用的内存。 I checked the memory usage with task manager; 我通过任务管理器检查了内存使用情况; however, I also used VisualVM and it wasn't showing any signs of there being a memory problem (the amount of bytes allocated remained consistent). 但是,我还使用了VisualVM,并且没有显示任何内存问题的迹象(分配的字节数保持一致)。 Anyways, I managed to simplify the problem to this trivial code: 无论如何,我设法将问题简化为以下简单代码:

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

            }

        });
    }
}

What I thought should happen is that when I press 1, a new double array would be allocated, but since it's only being allocated on the stack, once the method returns, the memory would be freed shortly after. 我认为应该发生的是,当我按1时,将分配一个新的double数组,但是由于它仅分配在堆栈上,因此一旦该方法返回,则不久后将释放内存。 However, this is not the case. 然而,这种情况并非如此。 Task manager shows a growing amount of memory being used for this application each time I press 1. Even forcing garbage collection has no effect. 每次按1时,任务管理器都会显示用于此应用程序的内存在不断增长。即使强制执行垃圾回收也没有效果。 So, what am I missing here? 那么,我在这里想念什么?

If for some reason you don't believe me, simply copy and paste it. 如果由于某种原因您不相信我,只需复制并粘贴即可。

What I thought should happen is that when I press 1, a new double array would be allocated, but since it's only being allocated on the stack, once the method returns, the memory would be freed shortly after. 我认为应该发生的是,当我按1时,将分配一个新的double数组,但是由于它仅分配在堆栈上,因此一旦该方法返回,则不久后将释放内存。

That is not correct. 那是不对的。

The double array is allocated on the heap. double数组在堆上分配。 All objects are allocated on the heap 1 . 所有对象都分配在堆1上

The reference to the object that is held in a local variable which will be on the stack. 对将在堆栈中的局部变量中保存的对象的引用。 By the time that the keyPressed() method returns, the local will have gone away, making the double array unreachable . keyPressed()方法返回时,局部keyPressed()将消失,使得double数组无法访问 But it won't actually be reclaimed until the GC gets around to it. 但是直到GC解决它,它实际上才被回收。

Under normal circumstances, the JVM only runs the GC when it detects that heap space is running out. 在正常情况下,JVM仅在检测到堆空间用完时才运行GC。 (The actual triggering condition depends on the kind of GC that you are using ...) You are trying to force the issue by calling System.gc() , and in your case 2 this will be triggering a GC. (实际的触发条件取决于您使用的GC的类型...。)您试图通过调用System.gc()来强制执行此问题,在您的情况2中,触发GC。

So ... the GC runs, and reclaims the space. 所以... GC运行,并回收空间。 How come you are not seeing this? 你怎么没看到这个? Well, you don't say how you are monitoring the JVM's memory usage, but I expect that you are using OS level tools (eg top, or the windows task monitors.) These tools won't see a drop in the process memory usage when the GC reclaims the space. 好吧,您没有说如何监视JVM的内存使用情况,但是我希望您使用的是OS级工具(例如top或Windows任务监视器)。这些工具不会看到进程内存使用率的下降当GC回收空间时。 That is because the GC does not normally "give back" the reclaimed space to the operating system. 这是因为GC通常不会将回收的空间“返还”给操作系统。 Rather, it keeps the space ready for the next time the application allocates a big object / array. 而是为下一次应用程序分配大对象/数组保留了空间。 (This makes Java memory management simpler and more efficient.) (这使Java内存管理更简单,更高效。)

A JVM can be configured to give back unneeded memory, via (yet another) JVM switch, but even that mechanism is reluctant. 可以将JVM配置为通过(另一个)JVM开关来释放不需要的内存,但是即使该机制也不愿意。 It takes a few GC cycles before the JVM will decide that it has more heap memory than it needs and give some back. JVM需要几个GC周期,JVM才会决定它具有比所需更多的堆内存并提供一些内存。

In summary: What you have here is not a "memory leak". 总结:您这里没有“内存泄漏”。 Rather it is evidence that Java does not reclaim space immediately, and that it is reluctant to "give back" any reclaimed heap space to the operating system. 而是有证据表明Java不会立即回收空间,并且它不愿意将任何回收的堆空间“返还”给操作系统。 It certainly won't give the memory back immediately. 当然,它不会立即恢复内存。


1 - There is JVM switch to enable something known as "escape analysis" in the JIT compiler. 1-有JVM开关可在JIT编译器中启用称为“转义分析”的功能。 This will cause it to try to identify cases where an object is allocated that cannot "escape" the local method call, and can therefore be allocated on the stack. 这将导致它尝试确定分配对象的情况,这些对象不能“转义”本地方法调用,因此可以在堆栈上分配。 However, I don't think it applies to arrays, and it certainly wouldn't apply to this array ... because the array is too big to allocate on a thread stack. 但是,我认为它不适用于数组,并且肯定不适用于此数组……因为该数组太大,无法在线程堆栈上分配。

2 - You will see references all over that place that calling System.gc() does not guarantee that the GC runs. 2-您将在整个地方看到调用System.gc()不能保证GC运行的引用。 This is true ... the javadocs say so. 这是真的……javadocs这么说。 However, in practice System.gc() will run the GC unless the JVM was launched with a flag that says "ignore System.gc()" calls. 但是, 实际上 ,除非使用带有“忽略System.gc()”调用的标志启动JVM,否则System.gc()将运行GC。

@TizianoPiccardi is right. @TizianoPiccardi是正确的。 If you do -Xmx128M as a command argument on your program, the amount of memory used shouldn't go above that 128MB limit, and would caused unused memory to be cleaned when the total memory reaches that limit. 如果在程序上使用-Xmx128M作为命令参数,则使用的内存量不应超过该128MB限制,并且当总内存达到该限制时,将导致未使用的内存被清除。 If your using eclipse, type the argument in Run Configurations -> Arguments -> VM Arguments 如果您使用eclipse,请在“运行配置”->“参数”->“ VM参数”中键入参数

An array is an object and all objects are allocated on the heap. 数组是一个对象,所有对象都分配在堆上。 The local reference to the array you created is on the stack. 对您创建的数组的本地引用在堆栈上。

System.gc() is just a suggestion to the Java VM that garbage collection should be run but it's not guaranteed to run. System.gc()只是对Java VM的建议,应该运行垃圾回收,但不能保证运行它。

You can use the Visual GC plugin of visualvm to see when gc happens. 您可以使用visualvmVisual GC插件来查看gc何时发生。 For me Eden space was cleaned when I pressed 2 of your code (or when I press 1 until Eden space is full). 对我来说,当我按代码的2时(或者当我按1直到Eden空间已满时),Eden空间被清理了。

In summary I see no leak here. 总而言之,我在这里没有发现泄漏。

So, my leaking problem turned out to not be a leaking problem at all. 因此,我的泄漏问题根本不是泄漏问题。 To test out what Stephen C explained, I used Streak324's and TizianoPiccardi's suggestion of setting the heap memory limit when the memory from the heap will actually be given back to the OS via -XMX256M. 为了测试Stephen C的解释,我使用了Streak324和TizianoPiccardi的建议来设置堆内存限制,当实际将堆中的内存通过-XMX256M返回给OS时。 My game now never goes over that limit. 我的游戏现在永远都不会超过这个极限。 In fact, if I had just ran my game long enough, it would have never went over 1024MB, but I never tried it. 实际上,如果我将游戏运行足够长时间,它永远不会超过1024MB,但我从未尝试过。 Anyways, thank you everyone. 无论如何,谢谢大家。

Where did u write -XMX256M? 您在哪里写-XMX256M? Is there a location where u can make this setting application specific? 您在哪里可以使此设置应用程序特定? Or did u just write it in general java settings, because then this limit is used for ANY java app u are running. 还是您只是在一般的Java设置中编写了它,因为此限制用于您正在运行的任何Java应用程序。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM