调试java中的内存问题 - 了解freeMemory

时间:2011-08-11 02:21:39

标签: java out-of-memory

我有一个导致OutOfMemoryError的应用程序,所以我尝试使用Runtime.getRuntime()。freeMemory()调试它。这是我得到的:

freeMemory=48792216
 ## Reading real sentences file...map size=4709. freeMemory=57056656
 ## Reading full sentences file...map size=28360. freeMemory=42028760
freeMemory=42028760
 ## Reading suffix array files of main corpus ...array size=513762 freeMemory=90063112
 ## Reading reverse suffix array files... array size=513762. freeMemory=64449240

我尝试了解freeMemory的行为。它从48 MB开始,然后 - 在我读取一个大文件后 - 它跳到最大57 MB,然后再次向下跳到42 MB,然后 - 在我读取一个非常大的文件(513762个元素)之后它跳到90 MB,然后再次下降到64 MB。

这里发生了什么?我怎样才能理解这些数字?

4 个答案:

答案 0 :(得分:7)

java内存有点棘手。你的程序在jvm里面运行,jvm在os里面运行,os使用你的计算机资源。当你的程序需要内存时,jvm会看到它是否已经向os请求了一些当前未使用的内存,如果内存不足,jvm会询问操作系统,如果可能的话,还会获得一些内存。

jvm会不时地查看不再使用的内存,并将释放它。根据(大量)因素,jvm还可以将该内存返回给操作系统,以便其他程序可以使用它。

这意味着,在任何给定时刻,你都有jvm从os中获得的一定数量的内存,以及jvm当前使用的一定数量的内存。

在任何给定的点上,jvm可能会拒绝获取更多的内存,因为它已经被这样做了,或者os可能会拒绝jvm访问更多的内存,要么是因为再次指示这样做,要么仅仅是因为没有更多的免费公羊。

当您在计算机上运行程序时,您可能没有对jvm进行任何限制,因此您可以使用大量内存。在谷歌应用程序上运行时,谷歌运营商可能会对jvm施加一些限制,因此可用的内容可能会更少。

Runtime.freeMemory会告诉你jvm从os获得的ram目前有多少是免费的。

当你分配一个大对象,比如一个MB时,jvm可能需要更多ram到os,比如5 MB,导致freeMemory比以前多4 MB,这是违反直觉的。分配另一个MB可能会按预期缩小可用内存,但后来jvm可能会决定向thenos释放一些内存,而freeMemory将再次收缩而没有明显的原因。

将totalMemory和maxMemory与freeMemory结合使用,可以更好地了解当前的ram限制和消耗情况。

要了解为什么消耗的ram超出预期,您应该使用内存分析器。一个简单但有效的包装与visualvm,一个通常已经与jdk一起安装的工具。在那里,你将能够看到你的程序中使用ram的原因以及为什么jvm无法回收内存。

(注意,jvm的内存系统远不如此复杂,但我希望这种简化可以帮助你理解更多完整而复杂的图片)

答案 1 :(得分:4)

它不是非常清晰或用户友好。如果你看一下运行时api,你会看到3个不同的内存调用:

  

freeMemory返回Java Virtual中的可用内存量   机。调用gc方法可能会导致值增加   由freeMemory返回。

     

totalMemory返回Java虚拟内存的总内存量   机。此方法返回的值可能会随时间而变化,   取决于主机环境。

     

maxMemory返回Java虚拟的最大内存量   机器将尝试使用。

启动jvm时,可以设置初始堆大小(-Xms)以及最大堆大小(-Xmx)。例如java -Xms100m -Xmx 200m以100m的堆开始,将增加堆,因为需要更多的空间,最多200个,如果需要增长超过它,将失败OutOfMemory。所以有一个天花板,它给你maxMemory()。

JVM中当前可用的内存介于起始和最大之间。 Somwhere。这是你的totalMemory()。 freeMemory()是多少是免费的。

为了增加混乱,看看他们对gc的看法 - “调用gc方法可能会导致增加freeMemory返回的值。”这意味着未收集的垃圾包含在空闲内存中。

答案 2 :(得分:3)

好的,根据你的评论,我写了这个函数,它打印了内存测量的摘要:

static String memory() {
    final int unit = 1000000; // MB
    long usedMemory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
    long availableMemory = Runtime.getRuntime().maxMemory() - usedMemory;
    return "Memory: free="+(Runtime.getRuntime().freeMemory()/unit)+" total="+(Runtime.getRuntime().totalMemory()/unit)+" max="+(Runtime.getRuntime().maxMemory()/unit+" used="+usedMemory/unit+" available="+availableMemory/unit);
}

似乎我的程序使用的最佳度量是usedMemory,以及补充的availableMemory。当我使用更多内存时,它们会单调增加/减少:

Memory: free=61 total=62 max=922 used=0 available=921
Memory: free=46 total=62 max=922 used=15 available=906
Memory: free=46 total=62 max=922 used=15 available=876
Memory: free=44 total=118 max=922 used=73 available=877
Memory: free=97 total=189 max=922 used=92 available=825

答案 3 :(得分:2)

尝试针对类似http://download.oracle.com/javase/1.5.0/docs/guide/management/jconsole.html的内容运行您的应用。 它附带了JVM(或者当然习惯使用),在监视应用程序执行期间监视JVM内部的情况方面非常有用。

它将为您的内存提供更多有用的见解,而不是调试语句。

另外,如果你非常热衷,你可以通过类似的东西学习更多关于调整垃圾收集的知识。 http://www.oracle.com/technetwork/java/gc-tuning-5-138395.html

这是非常深入的,但是深入了解JVM中各代内存以及如何在这些代中保留对象。如果你看到旧物种被保留下来并且老一代不断增加,那么这可能是泄漏的一个指标。

为了调试为什么要保留数据而不是收集数据,那么您无法通过分析器。查看JProfiler或Yourkit。

祝你好运。