JVM将内存返还给操作系统

19
我有一个关于JVM内存管理的问题(至少是针对SUN的)。
我想知道如何控制JVM将未使用的内存发送回操作系统(在我的情况下是Windows)。
我编写了一个简单的Java程序来说明我的期望。使用-Dcom.sun.management.jmxremote选项运行它,这样您可以使用jconsole等工具监视堆。
以下是程序内容:
package fr.brouillard.jvm;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.LinkedList;
import java.util.List;

public class MemoryFree {
    private BufferedReader reader = new BufferedReader(new
        InputStreamReader(System.in));
    private List<byte[]> usedMemory = new LinkedList<byte[]>();
    private int totalMB = 0;
    private int gcTimes = 0;

    public void allocate(int howManyMB) {
        usedMemory.add(new byte[howManyMB * 1024 * 1024]);
        totalMB += howManyMB;
        System.out.println(howManyMB + "MB allocated, total allocated: " +
                totalMB + "MB");
    }

    public void free() {
        usedMemory.clear();
    }

    public void gc() {
        System.gc();
        System.out.println("GC " + (++gcTimes) + " times" );
    }

    public void waitAnswer(String msg) {
        System.out.println("Press [enter]" + ((msg==null)?"":msg));
        try {
            reader.readLine();
        } catch (IOException e) {
        }
    }

    public static void main(String[] args) {
        MemoryFree mf = new MemoryFree();
        mf.waitAnswer(" to allocate memory");
        mf.allocate(20);
        mf.allocate(10);
        mf.allocate(15);
        mf.waitAnswer(" to free memory");
        mf.free();
        mf.waitAnswer(" to GC");
        mf.gc();
        mf.waitAnswer(" to GC");
        mf.gc();
        mf.waitAnswer(" to GC");
        mf.gc();
        mf.waitAnswer(" to GC");
        mf.gc();
        mf.waitAnswer(" to exit the program");

        try {
            mf.reader.close();
        } catch (IOException e) {}
    }
}

第一次进行垃圾回收后,内部堆会被释放(这是期望的),但只有在第三次垃圾回收后,内存才开始被发送回操作系统。在第四次之后,全部分配的内存都会被发送回操作系统。

如何设置JVM以控制此行为? 实际上,我的问题是我需要在服务器上运行多个CITRIX客户端会话,但我希望服务器上运行的JVM尽快释放内存(我的应用程序中只有少量高消耗内存的函数)。

如果无法控制此行为,我可以将其保留并增加操作系统虚拟内存,并让操作系统按照自己的方式使用它,而不会出现性能问题。 例如,在具有足够虚拟内存的4GB服务器上运行10个占用1GB内存的Java进程(仅具有100MB实际分配对象在堆中),是否会存在问题。

我想其他人已经遇到过这样的问题/困境。

谢谢您的帮助。


哇,标签完全吞掉了你的代码。 - John T
我稍微重新格式化了你的代码片段,以使用Stackoverflow的语法高亮功能 - 希望你不介意。 - Tamas Czinege
谢谢,我没有成功地将其正确显示。 我并不沮丧,我只是新来的;-) - Matthieu BROUILLARD
一个老问题,但我也希望看到更好的人体工程学设计,或者至少是外部控制堆提示给JVM,以更好地控制多个JVM实例及其各自的资源。 - Jé Queue
3个回答

15
为了控制堆内存的归还,自Java 5开始,可以使用 -XX:MaxHeapFreeRatio 选项,如 调优指南中所述。
如果您认为您的问题与这个问题有实质性的不同,请指出具体区别。

谢谢您的回复,我会根据这些提示进行一些测试。 - Matthieu BROUILLARD
似乎他的问题的一部分是“如果您有虚拟内存可以借助,过度分配机器内存是否安全”,这与链接的问题不同。 - Kai

7

首先,System.gc() 可能什么也不做。您真的不能依赖它按照您的建议执行垃圾回收。

其次,您需要使用监视GC实际进行的操作:

-verbosegc -XX:+PrintGCDetails 

在您调用 Java 或使用 JConsole 时,可能会出现 System.gc() 的情况,这让我担心您可能算错了垃圾回收的次数。请求GC不等于进行GC!请检查 PrintGCDetails 输出的日志(以这种方式解释)。 您的问题是需要在服务器上运行多个 CITRIX 客户端会话,但希望服务器上运行的 JVM 尽快释放内存(应用程序中只有少量高消耗内存的函数)。尽管您的问题是合理的,但您采取的解决方案有些靠不住。JVM 需要堆大小,以确保其有足够的空间运行。您似乎倾向于启动一个应用程序,然后等待 JVM 调整其堆大小,然后再启动另一个应用程序,这样您就超售了机器资源。不要这样做,因为一旦某个应用程序占用的内存超出您预计的范围,而它又有权使用更多的内存,那么所有东西都将崩溃。我完全相信您不想这样微观管理 Java 的堆。阅读足够的http://java.sun.com/docs/hotspot/gc5.0/gc_tuning_5.html,了解代和较大/较小堆的权衡。

这并没有回答问题,问题是关于操作系统和JVM之间的内存交换,而不是JVM内部的内存交换。 - erickson
@erickson - 你是对的,删除JVM内部堆注释。我认为他指定了一个过大的堆,但实际上不需要这么大,可以分配更小的。然而似乎并非如此。 - Kai

0

除非你看到明显的减速,否则不必担心它。如果一个进程分配了内存但没有使用,操作系统会根据需要将未使用的块交换到磁盘上。


2
那么您的建议是分配至少与我拥有的进程数量乘以每个进程允许的JVM堆大小相同的虚拟内存。例如,如果有10个最大堆为1GB的进程,则我需要大约10GB的虚拟内存? - Matthieu BROUILLARD
是的,让我们交换...这个想法真是太好了,完全可以杀死性能。 - LtWorf

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