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


225

即使很棘手,也可以用Java强制进行垃圾回收吗?我知道System.gc();Runtime.gc();但是他们只建议做GC。我该如何强制GC?


30
为您为什么需要强制使用GC提供一些背景知识可能会有所帮助。通常,在垃圾收集语言中,显式调用收集器是一种不好的做法。
贾斯汀·埃斯蒂尔2009年

3
给定的JVM可以提供几种垃圾回收方法,每种方法都有其自身的优缺点,并且经常可以通过在启动时提示JVM来避免给定的情况。请详细说明方案。
托尔比约恩Ravn的安徒生


5
这是一个强制垃圾回收的用例:我有一台具有30GB堆的服务器,通常使用约12GB(约500万个对象)。服务器每5分钟花费大约一分钟执行一次复杂的任务,其中使用了大约3500万个其他对象。每小时两次都会触发一次完整的GC,在复杂任务期间始终会触发两次,并将VM冻结10到15秒。我很乐意在没有执行复杂任务的情况下强制运行完整的GC。这样一来,就需要处理5M个活动对象,而不是40M个。
史蒂夫·

3
@JustinEthier在一个很明显的情况下,您可能想强制GC,它正在对涉及java.lang.ref.Reference类型层次结构的任何行为进行单元测试。
埃利亚斯·瓦斯连科

Answers:


168

最好的选择是调用System.gc(),这只是向垃圾收集器提示您要它进行收集。由于垃圾收集器是不确定的,因此无法强制立即收集。


28
应该有。non-deterministic == trouble
Pacerier,2014年

7
垃圾收集器可能是不确定的,仍然提供强制立即收集的方法。例如,通常.NET收集器是不确定的,但是对GC.Collect()的调用会强制其运行。只是Java选择不公开此功能。
PetrHudeček2015年

2
以我的经验,此方法总是调用垃圾收集器。这样做有足够的规律性,以至于我的内存使用与声明的对象数量的关系图始终严格线性(考虑填充等)。
Jim Pivarski

我在想,通过分配新对象然后不再引用它们,垃圾收集器将自动运行
Bionix1441 17-10-17

@PetrHudeček在现实世界中,.NET GC.Collect()无法收集。在Java gc()中。
ajeh

53

jlibs库中有垃圾收集一个很好的实用工具类。您可以使用带有WeakReference对象的漂亮技巧来强制进行垃圾收集。

来自jlib的RuntimeUtil.gc()

   /**
    * This method guarantees that garbage collection is
    * done unlike <code>{@link System#gc()}</code>
    */
   public static void gc() {
     Object obj = new Object();
     WeakReference ref = new WeakReference<Object>(obj);
     obj = null;
     while(ref.get() != null) {
       System.gc();
     }
   }

1
这段代码被破坏是因为弱引用在其引用变得弱可及之后即从内存中清除之前就被清除了。
Marko Topolnik 2012年

1
您可能会将“ GC已运行”的含义与“已回收内存”混为一谈。该对象仍然存在,甚至还没有完成,但是您不能再通过弱引用访问它了。更好的方法是将a PhantomReference与a 配合使用ReferenceQueue,然后在完成后但仍在清除之前得到通知。最后,即使您成功检测到该对象的内存已被回收,在像HotSpot这样的世代GC中,它的意义仍然很小。通常,它与年轻一代的清理工作相吻合。
Marko Topolnik,2012年

20
OP要求并且您声称提供了“强制垃圾收集”的解决方案。运行GC子系统是一回事,实际上收集垃圾是另一回事。您提供的代码示例显然旨在确保已收集垃圾。无论如何,这是一个非常老的问题,它显然与OP的愿望无关,而是对公众有用。没有人对自己“强制GC子系统运行”感兴趣,没有收集垃圾。实际上,人们通常希望保证已经收集了所有垃圾
Marko Topolnik

4
您可能没有将它与的效率进行比较System.gc(); System.gc();,但是确定它是否比它更好地工作肯定很有趣。实际上,只需打印它调用的次数就System.gc()足够了。达到2的机会很小。
Marko Topolnik

3
@MarkoTopolnik:“没有人对自己“强制GC子系统运行”感兴趣,没有收集到垃圾”……实际上,我今天对这种行为很感兴趣。我很感谢这个答案的存在。我的目的是检查GC日志处理的旋转行为,并获取GC输出的格式。这个小技巧可帮助我快速填写GC日志。
erik.weathers

49

强制GC的最佳方法(如果不是唯一的话)是编写自定义JVM。我相信垃圾收集器是可插入的,因此您可能只选择其中一种可用的实现并进行调整即可。

注意:这不是一个简单的答案。


40
+1为lulz。没有什么比有幽默感的人更令人沮丧的调试了。而不是实际可用的答案。
jsh 2012年


25

是的,几乎可以强迫您必须以相同的顺序调用方法,并且同时调用这些方法:

System.gc ();
System.runFinalization ();

即使只是一个对象,同时清除这两种方法的使用,也迫使垃圾回收器使用finalise()无法访问的对象的方法来释放分配的内存并执行该finalize()方法说明的操作。

但是,使用垃圾收集器是一种可怕的做法,因为使用垃圾收集器可能导致软件过载,甚至比内存还严重,因此垃圾收集器有自己的线程,无法控制,具体取决于gc所使用的算法可能会花费更多时间,并且效率很低,您应该在gc的帮助下检查软件是否最差,因为它肯定已损坏,好的解决方案一定不能依赖gc。

注意:请记住,仅当在finalize方法中不是重新分配对象时,此方法才有效,如果发生这种情况,对象将保持活动并具有技术上可能的复活。


10
,即使这两个命令也不会强制进行垃圾收集。正如其他人已经提到的,gc()这只是运行垃圾回收的提示。runFinalizers()仅对“已被发现丢弃的对象”运行终结器。如果gc并未真正运行,则可能没有此类对象...
Steffen Heil

同样,System.runFinalization()也不保证任何东西都会运行;有可能什么也不会发生。这是一个建议 -来自Javadoc:“ 调用此方法表明,Java虚拟机将花更多的精力来运行已发现被丢弃但尚未运行其finalize方法的对象的finalize方法
kaan

21

根据OutOfMemoryError的文档,它声明除非VM在完全垃圾回收后未能回收内存,否则不会抛出该内存。因此,如果在出现错误之前一直分配内存,那么您将已经强制执行完整的垃圾回收。

大概您真正想问的问题是:“我该如何回收我认为应该通过垃圾回收回收的内存?”


18

要手动请求GC(而不是从System.gc()):

  1. 转到:JDK中的bin文件夹,例如-C:\ Program Files \ Java \ jdk1.6.0_31 \ bin
  2. 打开jconsole.exe
  3. 连接到所需的本地进程。
  4. 转到内存选项卡,然后单击执行GC。

3
令人误解。请将鼠标悬停在“执行GC”按钮上。您可以请求JVM执行GC,但从不强制执行。
库马兰

@PinkeshSharma,这不强制。这只是一个请求,可能会完全忽略。
Pacerier,2014年

@Pacerier在理想世界中可以..但是,如果您执行此操作,您会发现内存立即增加……
Pinkesh Sharma 2015年

11

.gc是将来版本中淘汰的候选人-Sun工程师曾经评论说,全世界可能只有不到二十个人知道如何使用.gc()-昨晚我在中央/关键位置上做了一些工作使用SecureRandom生成的数据的数据结构,在40,000个对象之前,vm会变慢,就像指针用完了一样。显然,它阻塞了16位指针表,并表现出经典的“故障机制”行为。

我尝试了-Xms,依此类推,一直保持旋转状态直到它运行到大约57,xxx。然后,在gc()之后,它将使gc从57,127变为57,128-大约是Easy Money营地的代码膨胀速度。

您的设计需要基本的返工,可能是滑动窗口方法。


1
我有类似的东西,内存中有很多对象我无法释放它们。抛出OutOfMemory异常,我想强制GC测试是否存在某些“无限对象”创建过程,或者这些对象是系统使用的对象。

听起来您正在处理同一个问题,请解释:“无限对象创建” ...很好的研究项目,也许您可​​以在此处的Java区域中发布问题或其他内容(我在这里有点新意,不知道该站点如何运行的“有限自动机”)我昨天尝试并最终执行了file.dat,因为编译器在40,000 base36 BigIntegers上编码为静态最终String []时抱怨“太多代码” []我要坚持下去在这里并推测整个JVM限于16位指针,我敢打赌我们要做的是积极地将null并从磁盘中读取...
Nicholas Jordan

真的,我不明白你的意思。但是要弄清楚“无限对象创建”,我的意思是我的大系统上有一些代码可以创建处理并在内存中存活的对象,实际上我无法获得这部分代码,只是手势!

5
废话!有一种明显的情况应该使用它:测试使用弱引用的代码,以便我们可以确保清除弱引用后行为正确。
伊莱亚斯(Elias Vasylenko)2012年


6

JVM规范没有说明有关垃圾回收的任何具体信息。因此,供应商可以自由地以自己的方式实施GC。

因此,这种模糊性导致垃圾回收行为的不确定性。您应该检查JVM详细信息,以了解有关垃圾收集方法/算法的信息。也有一些选项可以自定义行为。


4

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


2

如果描述需要垃圾收集的原因,那会更好。如果您使用的是SWT,则可以处置诸如ImageFont释放内存的资源。例如:

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

还有确定未使用资源的工具。


如果没有任何处置方法怎么办?
ArifMustafa

1
完全无关的问题!不,我没有使用SWT。我正在调用JNI方法,该方法通过Delphi本机层打开.NET窗口。我还有一个FORTRAN计算核心,可以通过本机C ++层接收数据。那和什么有什么关系?我可以强制使用GC吗?没有?:-(
Mostafa Zeinali '18

0

如果您的内存用完了,OutOfMemoryException可以尝试使用java -Xms128m -Xmx512m而不是just 来开始编程,从而增加java可用的堆空间量java。这将使您的初始堆大小为128Mb,最大为512Mb,这远远超过标准的32Mb / 128Mb。


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

0

另一个选择是不创建新对象。

对象池已消失,以减少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) { }

}

0

在具有G1 GC的OracleJDK 10上,单个调用System.gc()将导致GC清理旧集合。我不确定GC是否可以立即运行。但是,即使System.gc()在循环中被多次调用,GC也不会清理Young Collection 。为了让GC清理Young Collection,您必须在循环中分配(例如new byte[1024])而不调用System.gc()System.gc()出于某种原因进行调用会阻止GC清理Young Collection。


0

真的,我不明白你的意思。但是要弄清楚“无限对象创建”,我的意思是我的大系统上有一些代码可以创建处理并在内存中存活的对象,实际上我无法获得这部分代码,只是手势!

这是正确的,只是手势。您已经有几张海报已经给出的标准答案。让我们一一介绍:

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

正确,没有实际的jvm-这只是一个规范,一堆描述所需行为的计算机科学...我最近研究了利用本机代码初始化Java对象的方法。为了获得您想要的,唯一的方法是执行所谓的主动空值。如果做错了错误,那是很糟糕的事情,以至于我们不得不将自己限制在问题的原始范围之内:

  1. 我的大型系统上的一些代码可以创建对象

这里的大多数张贴者都将假设您是在说您正在使用某个界面,如果这样的话,我们必须查看您是一次将整个对象还是一件物品交给了您。

如果不再需要对象,则可以为该对象分配空值,但是如果弄错了该对象,则会生成一个空指针异常。我敢打赌,如果您使用NIO,则可以实现更好的工作

任何时候您或我或任何其他人得到:“ 请我非常需要它。 ”它几乎是您正在努力工作的几乎完全破坏的普遍先兆....给我们写一个小样本代码,对其中的任何内容进行消毒实际使用的代码,并向我们显示您的问题。

不要沮丧。通常,这可以解决的问题是您的dba使用的是在某处购买的软件包,而原始设计并未针对海量数据结构进行调整。

这是很常见的。


-1

费耶

方法调用System.runFinalizersOnExit(true)保证在Java关闭之前调用终结器方法。但是,此方法本质上是不安全的,已被弃用。一种替代方法是使用Runtime.addShutdownHook方法添加“关闭挂钩”。

马萨拉特·西迪基(Masarrat Siddiqui)


关闭挂钩的缺陷在于它们很少真正起作用。强制终止不起作用,非零退出代码不起作用,有时(正式的)JVM仅在需要它们运行时才不运行它们。
ThePyroEagle

-1

有一些强制垃圾回收器的间接方法。您只需要用临时对象填充堆,直到执行垃圾收集器为止。我已经制作了以这种方式强制垃圾收集器的类:

class GarbageCollectorManager {

    private static boolean collectionWasForced;
    private static int refCounter = 0;

    public GarbageCollectorManager() {
        refCounter++;
    }

    @Override
    protected void finalize() {
        try {
            collectionWasForced = true;
            refCounter--;
            super.finalize();   
        } catch (Throwable ex) {
            Logger.getLogger(GarbageCollectorManager.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public int forceGarbageCollection() {
        final int TEMPORARY_ARRAY_SIZE_FOR_GC = 200_000;
        int iterationsUntilCollected = 0;
        collectionWasForced = false;

        if (refCounter < 2) 
            new GarbageCollectorManager();

        while (!collectionWasForced) {
            iterationsUntilCollected++;
            int[] arr = new int[TEMPORARY_ARRAY_SIZE_FOR_GC];
            arr = null;
        }

        return iterationsUntilCollected;
    }

}

用法:

GarbageCollectorManager manager = new GarbageCollectorManager();
int iterationsUntilGcExecuted = manager.forceGarbageCollection();

我不知道这种方法有多少用处,因为它会不断填充堆,但是如果您有必须强制使用GC的关键任务应用程序-这可能是Java便携式方法来强制使用GC。


什么是“ final int TEMPORARY_ARRAY_SIZE_FOR_GC = 200_000;”?
Koray Tugay,2015年

数组大小-将生成多少个临时对象(整数)以使GC开始工作。
Agnius Vasiliauskas 2015年

这是否有效:整数中的“ _”?
Koray Tugay 2015年

1
是的,从Java SE 7开始,数字文字中的下划线有效。例如,在这种情况下,用整数分隔千位很有用。
Agnius Vasiliauskas 2015年

3
永远不应在生产系统中运行此类代码。当此代码在一个线程中运行时,任何其他线程也可能会获得OutOfMemoryException,这完全颠倒了首先调用它的意图....
Steffen Heil

-1

我想在这里添加一些东西。请不要让Java在虚拟机而不是实际的机器上运行。虚拟机具有自己的与计算机通信的方式。它可能因系统而异。现在,当我们调用GC时,我们要求Java虚拟机调用垃圾收集器。

由于Garbage Collector随虚拟机一起使用,我们不能强迫它在那里进行清理。而是我们将请求与垃圾收集器排队。它取决于虚拟机,在特定时间(这可能会因系统而异,通常在分配给JVM的阈值内存已满时)之后,实际计算机将释放空间。:D


第二段的第一句话是不重要的
罗恩侯爵

-1

以下代码来自assertGC(...)方法。它试图强制非确定性垃圾收集器进行收集。

   List<byte[]> alloc = new ArrayList<byte[]>();
   int size = 100000;
   for (int i = 0; i < 50; i++) {
        if (ref.get() == null) {
             // Test succeeded! Week referenced object has been cleared by gc.
             return;
        }
        try {
             System.gc();
        } catch (OutOfMemoryError error) {
             // OK
        }
        try {
             System.runFinalization();
        } catch (OutOfMemoryError error) {
             // OK
        }

        // Approach the jvm maximal allocatable memory threshold
        try {
             // Allocates memory.
             alloc.add(new byte[size]);

             // The amount of allocated memory is increased for the next iteration.
             size = (int)(((double)size) * 1.3);
        } catch (OutOfMemoryError error) {
             // The amount of allocated memory is decreased for the next iteration.
             size = size / 2;
        }

        try {
             if (i % 3 == 0) Thread.sleep(321);
        } catch (InterruptedException t) {
             // ignore
        }
   }

   // Test failed! 

   // Free resources required for testing
   alloc = null;

   // Try to find out who holds the reference.
   String str = null;
   try {
        str = findRefsFromRoot(ref.get(), rootsHint);
   } catch (Exception e) {
        throw new AssertionFailedErrorException(e);
   } catch (OutOfMemoryError err) {
        // OK
   }
   fail(text + ":\n" + str);

来源(为清楚起见,我添加了一些评论):NbTestCase示例


-1

您可以尝试使用Runtime.getRuntime().gc()或使用实用程序方法System.gc()注意:这些方法不能确保GC。并且它们的范围应限于JVM,而不是在您的应用程序中以编程方式处理它。


2
如其他答案所述,这些方法不会强制(完整)垃圾收集运行。
流量

-2

如果您使用的是JUnit和Spring,请尝试将其添加到每个测试类中:

@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.