ChatGPT解决这个技术问题 Extra ChatGPT

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

是否可以在 Java 中强制进行垃圾收集,即使这样做很棘手?我知道 System.gc();Runtime.gc();,但他们只建议进行 GC。我怎样才能强制GC?

也许提供一些背景来解释为什么需要强制 GC 会有所帮助。通常在垃圾收集语言中,显式调用收集器是不好的做法。
一个给定的 JVM 可能提供几种垃圾收集方法,每种方法都有自己的优点和缺点,并且通常可以通过在启动时提示 JVM 来避免给定的情况。请详细说明场景。
这是一个强制垃圾回收的用例:我有一个 30GB 堆的服务器,其中通常使用 ~12GB(~5M 个对象)。每 5 分钟,服务器会花费大约一分钟来执行一项复杂的任务,其中使用了大约 35M 的附加对象。 Full GC 每小时触发几次,总是在复杂任务期间触发,并冻结 VM 10 到 15 秒。我很想在复杂任务没有运行的时候强制运行完整的 GC;然后它将处理 5M 活动对象而不是 40M。
@JustinEthier 在一个非常明显的情况下,您可能想要强制 GC,即对涉及 java.lang.ref.Reference 类型层次结构的任何行为进行单元测试。

J
Jim Pivarski

您最好的选择是调用 System.gc(),这只是向垃圾收集器提示您希望它进行收集。尽管垃圾收集器是不确定的,但没有办法强制立即收集。


应该有。 non-deterministic == trouble
垃圾收集器可能是不确定的,但仍然提供了一种强制立即收集的方法。例如,通常 .NET 收集器是不确定的,但调用 GC.Collect() 会强制它运行。只是Java选择不公开这个功能。
以我的经验,这个方法总是调用垃圾收集器。它这样做有足够的规律性,以至于我的内存使用与声明的对象数量的图总是严格线性的(考虑到填充等)。
我在想通过分配新对象然后不再引用它们,垃圾收集器会自动运行
@Pacerier 如果您想要确定性行为,现在可以使用 Epsilon 垃圾收集器。它确定性地从不收集任何东西。
F
Flow

jlibs library has a good utility class for garbage collection。您可以对 WeakReference 对象使用一个漂亮的小技巧来强制进行垃圾收集。

RuntimeUtil.gc() 来自 jlib:

   /**
    * 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();
     }
   }

这段代码被破坏了,因为当它的引用变得弱可达时,弱引用就会被清除,这是在它从内存中清除之前。
OP 要求并且您声称提供“强制垃圾收集”的解决方案。运行 GC 子系统是一回事,实际上收集垃圾是另一回事。您提供的代码示例显然有保证垃圾已被收集的意图。无论如何,这是一个非常古老的问题,显然不是关于 OP 的意愿,而是对公众的实用性。没有人对自己“强制 GC 子系统运行”感兴趣,没有垃圾被收集。事实上,人们通常希望保证所有垃圾都已被收集。
所以基本上,该代码没有比 System.gc() 更好的了。清除 WeakReference 与内存回收无关。我个人已经测试过该代码,发现它毫无用处。简单得离谱的代码 System.gc(); System.gc(); 的结果要好得多。
您可能没有将它与 System.gc(); System.gc(); 的效率进行比较,但知道它是否比这更好用肯定会很有趣。事实上,只打印它调用 System.gc() 的次数就足够了。达到2的机会非常渺茫。
@MarkoTopolnik:'没有人对自己“强制 GC 子系统运行”感兴趣,不收集垃圾'......实际上我今天只对这种行为感兴趣。我很感激这个答案是存在的。我的目的是检查 GC 日志处理的旋转行为,并获取 GC 输出的格式。这个小技巧帮助我快速填满了 GC 日志。
p
prashant

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

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


+1 为 lulz。没有什么比有幽默感的人更能让令人沮丧的调试了。除了一个实际可用的答案之外,就是这样。
t
trashgod

使用 Java™ Virtual Machine Tool Interface (JVM TI),函数

jvmtiError ForceGarbageCollection(jvmtiEnv* env)

将“强制 VM 执行垃圾回收”。 JVM TI 是 JavaTM Platform Debugger Architecture (JPDA) 的一部分。


l
legramira

是的,几乎有可能迫使您必须以相同的顺序调用方法,同时这些方法是:

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

即使只是清理一个对象同时使用这两种方法也会强制垃圾收集器使用 finalise() 方法来释放分配的内存并执行 finalize() 方法规定的不可访问对象的方法。

然而,使用垃圾收集器是一种糟糕的做法,因为使用它可能会给软件带来比内存更糟糕的过载,垃圾收集器有自己的线程,无法控制加上取决于gc 使用的算法可能需要更多时间并且被认为非常低效,如果它最糟糕,您应该在 gc 的帮助下检查您的软件,因为它肯定是坏的,一个好的解决方案不能依赖 gc。

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


NO,即使这两个命令也不会强制进行垃圾回收。正如其他人已经提到的,gc() 只是运行垃圾收集的提示。 runFinalizers() 仅对“已发现被丢弃”的对象运行终结器。如果 gc 没有实际运行,则可能没有此类对象...
此外,System.runFinalization() 并不能保证任何东西都会运行;有可能什么都不会发生。这是一个建议 – 来自 Javadoc:“调用此方法表明 Java 虚拟机花费精力来运行已发现已被丢弃但其 finalize 方法没有被丢弃的对象的 finalize 方法尚未运行"
P
Pete Kirkham

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

大概你真正想问的问题是“我怎样才能回收我认为应该通过垃圾收集回收的内存?”


N
Nathan

您可以从命令行触发 GC。这对于批处理/crontab 很有用:

jdk1.7.0/bin/jcmd <pid> GC.run

看 :

https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/tooldescr006.html


尝试添加一些关于参考的解释、评论或描述
根据文档,所有这些都是调用 System.gc(),并且正如多次声明的那样,System.gc() 不会强制进行垃圾收集。 docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/…
P
Pinkesh Sharma

手动请求 GC(不是来自 System.gc()):

Go To : JDK 中的 bin 文件夹 eg.-C:\Program Files\Java\jdk1.6.0_31\bin 打开 jconsole.exe 连接到所需的本地进程。转到内存选项卡并单击执行 GC。


Oppsss误导。请将鼠标悬停在“Perform GC”按钮上。您可以请求 JVM 执行 GC,但不要强制执行。
@PinkeshSharma,这并不强制。这只是一个请求,可能会被完全忽略。
@Pacerier 在理想的世界中是的.. 但是如果你这样做,你会发现内存立即增加了......
C
Cameron McKenzie

如何强制 Java GC

好的,这里有几种不同的方法来强制 Java GC。

点击 JConsole 的 Perform GC 按钮 使用 JMap 的 jmap -histo:live 7544 命令,其中 7544 是 pid 调用 Java 诊断控制台的 jcmd 7544 GC.run 命令 Call System.gc();在您的代码中调用 Runtime.getRuntime().gc();在你的代码中

https://i.stack.imgur.com/rJWUT.png

这些都不起作用

这是肮脏的小秘密。这些都不能保证有效。你真的不能force Java GC

Java 垃圾收集算法是非确定性的,虽然所有这些方法都可以激励 JVM 进行 GC,但实际上并不能强制它。如果 JVM 执行的操作太多,并且无法执行 stop-the-world 操作,这些命令要么会出错,要么会运行但实际上不会发生 GC。

if (input.equalsIgnoreCase("gc")) {
    System.gc();
    result = "Just some GC.";
}

if (input.equalsIgnoreCase("runtime")) {
    Runtime.getRuntime().gc();
    result = "Just some more GC.";
}

修复该死的问题

如果您遇到内存泄漏或对象分配问题,请修复它。将手指放在 Java Mission Control 的 Force Java GC 按钮上只会让事情变得更糟。使用 Java Flight Recorder 分析您的应用程序,在 VisualVM 或 JMC 中查看结果,然后解决问题。试图 force Java GC 是一个傻瓜游戏。

https://i.stack.imgur.com/K3sbr.png


我很惊讶有多少人忽略了 System.gc() 的一个有效用例,或者实际上强制进行了 GC:找出实际可访问的内存量。例如,您希望在性能测试期间这样做,并确定 -XmX 的基线值 - 显然您不希望或在生产中不需要它。
N
Nicholas Jordan

.gc 是在未来版本中消除的候选 - 一位 Sun 工程师曾经评论说,世界上可能只有不到 20 人真正知道如何使用 .gc() - 我昨晚在中央/关键上做了几个小时的工作使用 SecureRandom 生成数据的数据结构,在刚刚超过 40,000 个对象的某个地方,vm 会减慢速度,就好像它已经用完了指针一样。很明显,它在 16 位指针表上窒息,并表现出经典的“故障机器”行为。

我尝试了 -Xms 等等,一直在旋转,直到它运行到大约 57,xxx 一些东西。然后它会在 gc() 之后运行 gc 从 57,127 到 57,128 - 大约是 Easy Money 营地的代码膨胀速度。

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


我有类似的东西,内存中有很多对象我无法释放它们。抛出 OutOfMemory 异常,我想强制 GC 测试是否有一些无限对象创建过程或者这些对象是我的系统使用的对象。
听起来您正在解决与我相同的问题,请解释:“无限对象创建” ...很好的研究项目,也许您可以在此处的Java区域中发布问题或其他内容(我在这里是新来的,而不是知道该站点如何工作的“有限自动机”)我昨天尝试并最终执行了 file.dat,因为编译器抱怨 40,000 base36 BigIntegers 编码为静态最终 String[] 上的“代码过多”我要坚持下去这里并推测整个 JVM 仅限于 16 位指针,我敢打赌我们要做的就是积极地为空并从磁盘读取......
真的,我不明白你。但是要清楚“无限对象创建”,我的意思是在我的大系统中有一些代码可以创建可以处理并在内存中存活的对象,我实际上无法获得这段代码,只是手势!
废话!有一个明显的情况应该使用它:测试使用弱引用的代码,这样我们就可以确保当弱引用被清除时行为是正确的。
虽然 Sun 工程师可能在 2009 年或更早的时候告诉过您,但没有迹象表明它实际上会发生。现在是 2020 年底,在过去的 9 年 / 9 个 Java 版本中,gc() 尚未被弃用。弃用/删除会破坏太多现有的应用程序,因此无法认真考虑,IMO。
r
rai.skumar

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

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


好主意,我想知道是否有一个特定的 GC,其中 System.GC 可以保证“做某事”:)
您不能也不应该保证在其调用时进行 GC 扫描,因为存在多层引用,实际上是 GC 必须遍历的引用树才能分离树的每个单个节点(对对象的引用基本上),但仍然保持树有效。立即调用听起来不切实际。因为,无论何时调用,GC 都必须以链式分层方式清理垃圾,以免任何对象处于空引用状态。但是,清理我们的代码或重新思考逻辑是最好的选择,也是迄今为止最干净的选择。
B
Bob Kaufman

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


P
Paul Lammertsma

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

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

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


如果没有任何 dispose 方法怎么办?
与问题完全无关!不,我没有使用 SWT。我正在调用一个通过 Delphi 本机层打开 .NET 窗口的 JNI 方法。我还有一个 FORTRAN 计算核心,它通过本机 C++ 层接收数据。这和什么有什么关系?我可以强制GC吗?不? :-(
重新启动机器也是一种释放内存的方法,尽管我怀疑这个答案对你的帮助不仅仅是找到一种手动调用垃圾收集的方法。
A
Aaron T Harris

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

对象池是为了减少 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) { }

}

n
nabster

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


正如其他答案中所解释的,这些方法不会强制(完整)垃圾收集运行。
R
Rahul Babu

我们可以使用 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()
M
Mike Nakis

我做了一些实验(参见 https://github.com/mikenakis/ForcingTheJvmToGarbageCollect),尝试了十几种不同的方式来执行垃圾收集,包括这个答案中描述的方式等等,我发现绝对没有任何方法可以确定性地强制 JVM 执行完成垃圾回收。即使是对这个问题的最佳答案也只是部分成功,因为他们实现的最佳结果是 some 垃圾收集,但从来没有保证完全垃圾收集。

我的实验表明,以下代码片段产生了最好(最差)的结果:

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 的创建者如此狂妄自大,以至于认为他们比我们更清楚我们是否想要这样做,或者我们是否应该这样做。不要那么嚣张。如果某些东西像魔术一样起作用,那么必须提供一些绕过魔术的方法。


V
Viktor Dahl

如果内存不足并获得 OutOfMemoryException,您可以尝试通过使用 java -Xms128m -Xmx512m 而不是仅 java 启动程序来增加 Java 可用的堆空间量。这将为您提供 128Mb 的初始堆大小和最大 512Mb,这远远超过标准的 32Mb/128Mb。


默认内存设置为 java -Xms512M -Xmx1024M
S
Sri9911

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

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

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

正确,没有实际的 jvm - 这只是一个规范,一堆描述所需行为的计算机科学......我最近研究了从本机代码初始化 Java 对象。为了得到你想要的,唯一的方法是做所谓的积极归零。如果做错了,那么错误是非常糟糕的,以至于我们不得不将自己限制在问题的原始范围内:

我的大系统中的一些代码创建对象

这里的大多数海报都会假设您说您正在处理一个界面,如果是这样,我们将不得不查看您是一次收到整个对象还是一个项目。

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

任何时候你、我或其他人得到:“求求你了,我非常需要那个。”它几乎是几乎完全破坏您正在尝试的工作的普遍前兆....给我们写一个小示例代码,从中清除任何实际使用的代码并向我们展示您的问题。

不要沮丧。这通常会解决您的 dba 使用从某处购买的软件包,并且原始设计并未针对大量数据结构进行调整。

这是很常见的。


在 Hotspot 之前,积极的归零曾经对 JVM 很有用。之后,我们一直有一个复制收集器,其中死对象的数量无关紧要。
M
Masarrat Siddiqui

供参考

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

马萨拉特·西迪基


关闭钩子的缺陷是它们很少真正起作用。强制终止不起作用,非零退出代码不起作用,有时(官方)JVM 根本不会运行它们,只要您需要它们运行。
A
Agnius Vasiliauskas

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

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 的关键任务应用程序 - 这可能是强制 GC 的 Java 可移植方式。


什么是“最终 int TEMPORARY_ARRAY_SIZE_FOR_GC = 200_000;”?
数组大小 - 将为 GC 开始工作生成多少临时对象(int)。
是的,underscores in numeric literals 从 Java SE 7 开始有效。例如,在这种情况下,这可用作整数中的千位分隔符。
你永远不应该在生产系统中运行这样的代码。虽然此代码在一个线程中运行,但任何其他线程也可能会收到 OutOfMemoryException,但首先完全颠倒了调用它的意图......
@Steffen Heil:……它可能会永远运行,因为垃圾收集不需要总是收集所有对象(并且很容易优化此处的数组创建)。此外,垃圾收集与终结不同,终结器不需要及时运行或根本不需要运行。将单个未决对象的最终确定推迟到该循环结束是完全合理的行为。这导致循环永远不会结束……
S
Saubhagya Ranjan Das

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

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


第二段的第一句是不合逻辑的。
这个答案完全混淆了“Java虚拟机”(这是一个字节码解释器)和“虚拟机”(这是一种在另一个处理器上模拟处理器的方法)。不,JVM 没有将 GC 委托给较低级别的机器,它有自己的 GC 例程。
N
Nathan

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


A
Aliuk

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

@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)