Java方法调用的内存使用情况

5
我在codeeval上尝试解决一个问题,但是使用了太多的内存。我的代码中有一个循环,由于一个大输入是不可避免的,它会运行很多次(~10,000^2)。我注意到,如果我运行循环并且在每次迭代中什么都不做,我总共使用大约6MB的内存。然而,如果我在循环中添加一个简单的方法调用,该方法只调用返回false的函数,我的内存使用量会跳到20MB。
为什么会这样?难道分配给每个函数调用的内存在函数调用完成后不会被释放吗?
编辑: 完整的代码非常庞大,与此无关,但这段代码片段就是我所描述的。如果我不包括foo()调用,则我的代码作为一个整体使用6MB的内存。如果我包括foo()调用,则我的代码作为一个整体使用20MB的内存。在我的实际代码中,foo()方法确实做了同样的事情(返回false),因为我想测试一下内存使用情况。
这是codeeval上的编码挑战,所以应该可以用他们允许的任何语言来解决问题,所以Java应该没问题。
编辑:我重构了一些代码,以便我可以提取出一个完整的函数来向你展示。这仍然会产生前面描述的相同结果。产生奇怪行为的函数调用是are_friends()
ArrayList<ArrayList<Integer>> graph(String[] word_list) {

    ArrayList<ArrayList<Integer>> adj_list = new ArrayList<ArrayList<Integer>>();

    for (int i = 0; i < word_list.length; i++) {
        adj_list.add(new ArrayList<Integer>());
    }

    for (int i = 0; i < word_list.length; i++) {
        for (int j = i + 1; j < word_list.length; j++) {
            if (are_friends(word_list[i], word_list[j])) {
                adj_list.get(i).add(j);
                adj_list.get(j).add(i);
            }
        }
    }

    return adj_list;
}

boolean are_friends(String a, String b) {
    return false;
}

2
请发布您的相关代码,最好能够编译运行。 - skiwi
2
内存会在 GC 决定何时释放它时被释放。您唯一拥有的保证是,在抛出 OutOfMemoryError 之前,它会尝试释放内存。展示您的代码。(但如果 20MB 太大,您可能不应该使用 Java) - JB Nizet
你可以尝试重复利用你的变量。尝试在循环外部声明你的布尔变量,然后只在内部设置其值即可。 - indivisible
内存消耗是否与输入大小呈线性比例?1000个的使用量是多少?2000个呢?4000个呢? - Ted Bigham
@user1529956,我认为反复调用 foo 不太可能会有任何内存影响。你能否尝试展示一个完整可运行的例子来重现这个问题? - assylias
显示剩余11条评论
3个回答

1
如果我加入foo()调用,我的代码整体使用20MB内存。
关于Java程序的内存使用,你应该小心确定性的说法。
你是指保留内存吗?还是指“我在任务管理器/顶部/其他进程监视工具中看到的"?或者是指“我使用VisualVM或类似工具进行了剖析,并且这是峰值堆使用情况”?
使用每个方法,你可能得到截然不同的测量结果。
内存使用的一个相关指标是使用 -Xmx 设置最大堆大小,比如16MB,并查看你的程序是否能够以其一种或另一种形式无错误地完成。请注意,这仅限制堆而不是JVM使用的任何其他支持内存区域的栈。
没有像上述那样限制堆,JVM可以自由地使用它认为合适的堆,保留很多垃圾以避免GC停顿。

当我在CodeEval上运行我的代码时,它只告诉我它使用了约20MB的内存。我说约20MB是因为它给出了确切的字节数,在多次尝试后,我注意到它平均约为20MB。 - user1529956
1
那个数据作为任何解释的基础都是无用的。要么找到权威规范,确切地说明他们的数字测量了什么,要么在自己的设置上重复测量。 - Marko Topolnik

0
您遇到的问题是,现在它将首先调用一个方法,即foo(),每次循环运行都会调用多次。
而方法最终会出现在调用堆栈上,并需要额外的时间进行处理,如果需要更深入的解释,您需要去谷歌搜索了。
重点是,当您将return false放在bar()内部时,它不会管理调用堆栈,因此使用的内存较少,可能更快。
我相信,在某些情况下,如果在Hotspot JVM(默认值)上运行,则JVM将内联您的foo()方法调用,从而导致行为就像您直接在bar()中有一个return false一样。优化的时间取决于JVM参数和您的特定版本/系统是否进行优化。 然而即使它已经优化,内存也已被JVM占用。即使JVM不再使用该内存,它也会拒绝将其归还给您的操作系统,因此您仍然观察到更高的内存使用率。

比较public static void main(String[] args) { while(true) { if (foo()) break; } }public static void main(String[] args) { while(true) { if (false) break; } }的内存使用情况。在我的机器上没有任何区别......一个显而易见的原因是foo()将很快被内联,使得两个代码在汇编级别上完全相同。即使没有内联,调用堆栈始终只有一个级别,并且我不认为这会使用任何重要的内存量。 - assylias
@assylias,你如何在本地查看内存使用情况?也许如果我能这样做而不是像我一直在做的那样提交到codeeval来查看,我可以更好地调试它。 - user1529956
@user1529956 可视化虚拟机 - assylias

0
每个函数调用分配的内存不应该在函数调用结束后被释放吗?
不是这样的。用于调用函数的内存位于堆栈上。该堆栈在线程启动时分配,并且直到线程退出时才被释放。
为什么会这样呢?
我认为您没有提供问题的所有相关信息。我刚试图在CodeEval上重现问题,并且在嵌套循环中和没有嵌套循环的情况下得到了相同的内存使用量(在500K以内)。
值得注意的是,相同的代码在CodeEval上产生不同的内存结果,从运行到运行都有所不同。虽然我没有看到像您看到的那样疯狂的偏差,但显然涉及到的因素不仅仅是代码。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接