在Java中查找内存使用情况

6
以下是我需要解决的情况。我遇到了两个解决方案。
我需要维护一个从数据库中获取并显示在Swing GUI上的数据缓存。每当我的JVM内存超过其分配内存的70%时,我需要警告用户使用过度。一旦JVM内存使用超过80%,然后我必须停止所有数据库查询并清理作为用户操作的现有缓存,并通知用户。在清理过程中,我将根据某些规则手动处理删除一些数据,并指示JVM进行GC。每当GC发生时,如果内存清理并达到分配内存的60%,我需要重新启动所有的数据库操作并将控制权交还给用户。
为了检查JVM内存统计信息,我找到了以下两种解决方案。无法决定哪种方法最好以及原因。
1. Runtime.freeMemory() - 创建线程每10秒运行一次,检查可用内存,如果内存超出限制,则必要的弹出窗口将通知用户并调用方法停止操作并释放内存。
2. MemoryPoolMXBean.getUsage() - Java 5引入了JMX来在运行时获取内存快照。在JMX中,我不能使用阈值通知,因为它只会在内存达到/超过给定阈值时通知。唯一的方法是在MemoryMXBean中使用轮询并在一段时间内检查内存统计信息。
在使用轮询的情况下,对我来说这两种实现方式似乎是相同的。
请建议方法的优点以及是否有其他替代方法/使用方法的任何更正。
8个回答

15

仅作为一则旁注:Runtime.freeMemory()并不表示可用于分配的剩余内存量,它只是当前已分配内存中空闲的内存量(最初比虚拟机配置使用的最大内存要小),但会随时间增长而增加。

启动虚拟机时,最大内存(Runtime.maxMemory())只是定义了虚拟机可以分配的内存上限(可使用-Xmx虚拟机选项进行配置)。 总内存大小(Runtime.totalMemory())是为虚拟机进程分配的内存的初始大小(可使用-Xms虚拟机选项进行配置),并且每次你分配超过当前空闲部分(Runtime.freeMemory())的内存时,它都会动态增长直到达到最大内存。

您感兴趣的指标是可用于进一步分配的内存:

long usableFreeMemory= Runtime.getRuntime().maxMemory()
    -Runtime.getRuntime().totalMemory()
    +Runtime.getRuntime().freeMemory()

或者:

double usedPercent=(double)(Runtime.getRuntime().totalMemory()
    -Runtime.getRuntime().freeMemory())/Runtime.getRuntime().maxMemory()

7
处理这种情况的通常方法是使用 WeakReferenceSoftReference。你需要同时使用两者 - 弱引用表示你不会持有多个副本,软引用表示 GC 将保留这些内容,直到内存开始不足。
如果需要进行额外的清理,则可以将引用添加到队列中,并重写队列通知方法以触发清理。这很有趣,但你确实需要了解这些类的作用。

5

当JVM在进行垃圾回收后,内存使用率会达到100%,然后回落到10%左右,这种情况是完全正常的,每隔几秒钟都会发生。

您不需要尝试以这种方式管理内存。在运行完整个GC之前,无法确定有多少内存被保留。

我建议您弄清楚自己真正想要实现什么,并从另一个角度来看待问题。


在我看来,这并不完全正常。这些参数可以进行调整(例如池大小、完整GC间隔等)。 - Yoni Roit

4
您提到的要求与JVM中垃圾回收的工作方式明显相矛盾。
由于JVM的行为,很难以正确的方式警告您的用户。完全停止数据库操作、清理东西并重新开始并不是正确的方法。
让JVM做它应该做的事情,处理所有与内存相关的问题。现代的JVM非常擅长这方面的工作,通过对GC参数进行微调,您将获得比自己强制执行更干净的内存管理。
http://www.kodewerk.com/advice_on_jvm_heap_tuning_dont_touch_that_dial.htm这样的文章提到了利弊,并提供了一个很好的解释,说明VM为您做了什么。

2

我只使用过第一种方法来完成类似的任务,效果还可以。

无论使用哪种方法,你都应该注意实现某种形式的去抖动 - 即一旦你意识到已经使用了70%的内存,等待一分钟(或者任何其他你认为合适的时间) - GC 可以在那个时候运行并清理大量内存。

如果在系统中实现 Runtime.freeMemory() 图表,你会看到内存不断上下波动。


1

VisualVM比JConsole更好一些,因为它提供了一个漂亮的可视化垃圾回收器视图。


0

非常晚了,我知道,但我想发一个我是如何做到的例子。希望对某人有用(我强调,这只是一个原则性的例子,没有别的...也不是特别优雅 :))

只需将这两个函数放入一个类中,它就应该可以工作。

编辑:哦,还有import java.util.ArrayList; import java.util.List;

public static int MEM(){
    return (int)(Runtime.getRuntime().maxMemory()-Runtime.getRuntime().totalMemory() +Runtime.getRuntime().freeMemory())/1024/1024;
}

public static void main(String[] args) throws InterruptedException
{
    List list = new ArrayList();

    //get available memory before filling list
    int initMem = MEM();
    int lowMemWarning = (int) (initMem * 0.2);
    int highMem = (int) (initMem *0.8);


    int iteration =0;
    while(true)
    {
        //use up some memory
        list.add(Math.random());

        //report
        if(++iteration%10000==0)
        {
            System.out.printf("Available Memory: %dMb \tListSize: %d\n", MEM(),list.size());

            //if low on memory, clear list and await garbage collection before continuing
            if(MEM()<lowMemWarning)
            {
                System.out.printf("Warning! Low memory (%dMb remaining). Clearing list and cleaning up.\n",MEM());

                //clear list
                list = new ArrayList();  //obviously, here is a good place to put your warning logic

                //ensure garbage collection occurs before continuing to re-add to list, to avoid immediately entering this block again
                while(MEM()<highMem)
                {
                    System.out.printf("Awaiting gc...(%dMb remaining)\n",MEM());
                    //give it a nudge
                    Runtime.getRuntime().gc(); 
                    Thread.sleep(250);
                }

                System.out.printf("gc successful! Continuing to fill list (%dMb remaining). List size: %d\n",MEM(),list.size());
                Thread.sleep(3000); //just to view output
            }
        }
    }
}

编辑:这种方法仍然依赖于使用-Xmx合理设置jvm的内存。

编辑2:看起来gc请求行确实有助于加快事情进展,至少在我的jvm上是这样。你的情况可能会有所不同。


0

看看JConsole。它可以绘制你需要的信息,所以只需要根据你的需求进行适应(前提是你在运行Sun Java 6)。

这还允许你将监视过程与你想查看的内容分离。


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