如何在Java中强制进行垃圾回收?

270

在Java中是否可以强制进行垃圾回收?即使有些棘手,我知道System.gc();Runtime.gc();但它们只是建议进行GC。那么如何强制GC呢?


32
或许为什么需要强制执行GC提供一些背景会有帮助。通常在垃圾收集语言中,显式调用垃圾收集器是一个不好的做法。请注意保持原意,同时使翻译更加易懂,不要添加解释和其他内容。 - Justin Ethier
3
一些JVM可能提供多种垃圾回收方法,每种方法都有其优缺点。通常情况下,只需在启动时提示JVM,就可以避免出现特定的情况。请详细说明这种情景。 - Thorbjørn Ravn Andersen
3
使用命令 "jmap -histo:live <pid>" 时,是否会强制进行垃圾收集?可参考这个链接:https://dev59.com/Q2w15IYBdhLWcg3wv-ba - user2463941
9
这是强制进行垃圾回收的一个应用场景:我有一个带有30GB堆的服务器,其中约有12GB通常被使用(大约5M个对象)。每5分钟,服务器会花费大约一分钟执行一个复杂任务,其中大约使用了35M个额外的对象。几乎每小时都会触发几次完整的垃圾回收,总是在执行复杂任务时发生,并且会导致VM冻结10到15秒钟。我希望能够在不运行复杂任务的时间强制运行完整的垃圾回收;这样,它将只需要处理5M个活跃对象而不是40M个。 - Steve
5
@JustinEthier 有一个非常明显的情况,你可能想要强制进行GC,那就是单元测试涉及java.lang.ref.Reference类型层次结构的任何行为。 - Elias Vasylenko
显示剩余5条评论
25个回答

7

JVM规范没有具体说明垃圾回收。因此,供应商可以自由地按照自己的方式实现GC。

因此,这种含糊不清会导致垃圾回收行为的不确定性。您应该检查JVM详细信息以了解垃圾回收方法/算法。此外,还有定制行为的选项。


1
好主意,我想知道是否有一个特定的GC,在其中System.GC保证会“做些什么” :) - rogerdpack
您在调用GC时不能保证进行垃圾回收,因为有多层引用,实际上是一棵引用树,GC必须遍历整个树来分离每个单独的节点(基本上是对象的引用),同时保持树的有效性。立即调用听起来并不实际;因为无论何时调用,GC都必须以链接的分层方式清理垃圾,以避免将任何对象留在空引用状态中。但是,清理我们的代码或重新思考逻辑是最好的选择,也是最干净的选择。 - Abhay Nagaraj

4
如果您需要强制进行垃圾回收,也许您应该考虑如何管理资源。您是否创建了在内存中持续存在的大型对象?是否创建了具有Disposable接口但在使用完后未调用dispose()方法的大型对象(例如图形类)?您是否在类级别上声明了仅在单个方法中需要的内容?

1
另一个选择是不创建新对象。
对象池是减少Java中GC需求的一种方法。
对象池通常不会比对象创建更快(尤其是对于轻量级对象),但它比垃圾回收更快。如果您创建了10,000个对象,每个对象为16字节。那就是160,000字节GC需要回收。另一方面,如果您不需要同时使用所有10,000个对象,则可以创建一个池来回收/重用对象,这消除了构造新对象的需要,并消除了GC旧对象的需要。
像这样(未经测试)。 如果您希望它是线程安全的,可以将LinkedList替换为ConcurrentLinkedQueue。
public abstract class Pool<T> {
    private int mApproximateSize;
    private LinkedList<T> mPool = new LinkedList<>();

    public Pool(int approximateSize) {
        mApproximateSize = approximateSize;
    }

    public T attain() {
        T item = mPool.poll();
        if (item == null) {
            item = newInstance();
        }
        return item;
    }

    public void release(T item) {
        int approxSize = mPool.size(); // not guaranteed accurate
        if (approxSize < mApproximateSize) {
            recycle(item);
            mPool.add(item);
        } else if (approxSize > mApproximateSize) {
            decommission(mPool.poll());
        }
    }

    public abstract T newInstance();

    public abstract void recycle(T item);

    public void decommission(T item) { }

}

1

如果您能描述一下为什么需要垃圾回收,那会更好。如果您正在使用SWT,可以释放资源(如ImageFont)以释放内存。例如:

Image img = new Image(Display.getDefault(), 16, 16);
img.dispose();

还有一些工具可以确定未处理的资源。


如果没有任何dispose方法怎么办? - ArifMustafa
1
完全与问题无关!不,我没有使用SWT。我正在通过Delphi本地层调用打开.NET窗口的JNI方法。我还有一个FORTRAN计算核心,通过本地C ++层接收数据。这与任何事情有什么关系?我能强制进行垃圾回收吗?不能吗? :-( - Mostafa Zeinali
重新启动机器也是释放内存的一种方式,尽管我怀疑这个答案并没有帮助你比手动调用垃圾回收更多。 - Paul Lammertsma

1
你可以尝试使用 Runtime.getRuntime().gc() 或使用实用程序方法 System.gc()。注意:这些方法不能确保垃圾回收,并且它们的范围应该限制在JVM而不是在应用程序中编程处理它。

2
如其他答案所解释的那样,这些方法并不会强制进行(完整的)垃圾回收运行。 - Flow

1
我们可以使用Java运行时来触发jmap -histo:live <pid>。这将强制对堆进行完全GC以标记所有活动对象。
public static void triggerFullGC() throws IOException, InterruptedException {
    String pid = ManagementFactory.getRuntimeMXBean().getName().split("@")[0];
    Process process = Runtime.getRuntime().exec(
            String.format("jmap -histo:live %s", pid)
    );
    System.out.println("Process completed with exit code :" + process.waitFor());
}

我尝试了这个,结果并不比 System.gc() 好。 - Mike Nakis

0

我进行了一些实验(参见https://github.com/mikenakis/ForcingTheJvmToGarbageCollect),尝试了大约十多种不同的垃圾收集方式,包括此答案中描述的方式以及其他方式,但我发现没有任何可靠的方式可以确定性地强制JVM进行完整的垃圾回收。即使是对于这个问题的最佳答案,它们所能实现的也只是部分成功,最好的结果也只是进行了一些垃圾回收,但从未能够保证进行完全的垃圾回收。

我的实验证明,以下代码片段产生了最佳(最少糟糕)的结果:

public static void ForceGarbageCollection()
{
    long freeMemory = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed();
    for( ; ; )
    {
        Runtime.getRuntime().gc();
        Runtime.getRuntime().runFinalization();
        long newFreeMemory = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed();
        if( newFreeMemory == freeMemory )
            break;
        freeMemory = newFreeMemory;
        sleep( 10 );
    }
}

sleep()函数的作用如下:

private static void sleep( int milliseconds )
{
    try
    {
        Thread.sleep( milliseconds );
    }
    catch( InterruptedException e )
    {
        throw new RuntimeException( e );
    }
}

很遗憾,sleep(10) 中的那个数字10是有魔力的;它假定你每秒进行适量的内存分配,并且还会产生适度的最终化。如果你更快地通过对象,则10可能不足,你可能需要等待更长时间。你可以将其设置为100来确保,但无论你设置成什么,总会有一个机会,它可能不够。

话虽如此,在一个受控环境中,10就足够了。这种方法被观察到能够始终从内存中清除所有不可达的对象,而在本问答中提到的其他任何方法都不能做到。我在github上链接的实验代码证明了这一点。

在我看来,Java虚拟机没有提供一种强制性、无条件、确定性、彻底、全面停顿的垃圾回收方法,因此它是有缺陷的

换句话说,JVM的创造者们非常自负,认为他们比我们更清楚我们是否想要这样做或者是否应该想要这样做。不要如此傲慢。如果某些东西看起来像魔法一样奏效,那么必须提供绕过魔法的方法。

0

我想强制进行垃圾回收,因为当它发生时我的代码被冻结了很长时间。目的是通过定期触发gc来平滑负载。

然而,在我的环境中,列出的解决方案并没有强制执行任何操作。

  • 所以:
  • 我请求临时变量的内存,
  • 简单地递增,
  • 并监控内存,一旦触发gc就停止操作。

这很容易实现,但需要调整。

Runtime rt = Runtime.getRuntime();
double usedMB = (rt.totalMemory() - rt.freeMemory()) / 1024 / 1024;

if (usedMB > 1000) // only when necessary
{
byte[][] for_nothing = new byte[10][];

for (int k = 0; k < 10 ; k ++)
    for_nothing[k] = new byte[100_000_000];
}

System.gc();
Runtime.getRuntime().gc();
Runtime.getRuntime().runFinalization(); 


-1
如果你的内存不足并且出现了OutOfMemoryException错误,你可以尝试通过使用java -Xms128m -Xmx512m来启动程序,而不是仅仅使用java。这将为你提供一个初始堆大小为128Mb和最大堆大小为512Mb的空间,远远超过标准的32Mb/128Mb。

默认的内存设置为 java -Xms512M -Xmx1024M - ThePyroEagle

-1
真的,我不明白你的意思。但是为了更清楚地解释“无限对象创建”,我的大型系统中有一些代码会创建处理和存活在内存中的对象,我实际上无法获得这段代码,只能猜测!

这是正确的,只是猜测。您已经得到了几位发布者给出的标准答案。让我们逐个来看:

  1. 我实际上无法获得这段代码

正确的,没有实际的jvm - 这只是一个规范,一堆计算机科学描述所需的行为...我最近深入研究了从本地代码初始化Java对象。要获得您想要的内容,唯一的方法是执行所谓的积极空值操作。如果做错了,错误是如此严重,我们必须限制自己在问题的原始范围内:

  1. 我的大型系统中有一些代码会创建对象

这里的大多数发布者都会认为您正在使用接口工作,如果是这样,我们需要查看您是否被交付整个对象还是一次一个项目。

如果您不再需要一个对象,可以将null赋值给该对象,但如果操作不当,则会生成空指针异常。如果您使用NIO,我敢打赌您可以实现更好的工作。

每当您、我或其他任何人都说:“请帮我,这太可怕了。”时,几乎总是意味着您正在尝试处理的内容即将被彻底摧毁……请为我们编写一小段示例代码,并从中清除任何实际使用的代码并展示您的问题。

不要感到沮丧。通常情况下,这意味着您的数据库管理员正在使用从其他地方购买的软件包,而原始设计并未针对大型数据结构进行调整。

这是非常普遍的情况。


过去在 Hotspot 之前,JVM 中的 Aggressive nulling 技术曾经非常有用。但是现在我们使用了复制收集器,因此死亡对象的数量已经不再重要。 - toolforger

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