Guide to System.gc() – System.gc()指南

最后修改: 2019年 9月 10日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this tutorial, we’re going to investigate the System.gc() method located in the java.lang package.

在本教程中,我们将研究位于java.lang包中的System.gc()方法。

Explicitly calling System.gc() is known for being a bad practice. Let’s try to understand why and if there are any use cases when calling this method might be useful.

明确调用System.gc()是众所周知的糟糕做法。让我们试着了解一下原因,以及在任何情况下调用这个方法是否有用。

2. Garbage Collection

2.垃圾收集

The Java Virtual Machine decides to perform garbage collection when there are indications to do so. Those indications differ from one GC implementation to another. They are based on different heuristics. However, there are a few moments when GC will be executed for sure:

当有迹象表明需要进行垃圾收集时,Java虚拟机就会决定进行垃圾收集。这些指示在不同的GC实现中是不同的。它们是基于不同的启发式方法。然而,有几个时刻,GC是肯定会被执行的。

  • Old generation (Tenured space) is full, which triggers major/full GC
  • New generation (Eden + Survivor0 + Survivor1 spaces) is full, which triggers minor GC

The only thing that is independent of the GC implementation is object eligibility to be garbage collected.

唯一独立于GC实现的东西是对象的垃圾收集资格。

Now, we’ll have a look at the System.gc() method itself.

现在,我们来看看System.gc()方法本身。

3. System.gc()

3. System.gc()

An invocation of the method is simple:

对该方法的调用很简单。

System.gc()

The official Oracle documentation states that:

官方的Oracle文档指出,。

Calling the gc method suggests that the Java Virtual Machine expend effort toward recycling unused objects in order to make the memory they currently occupy available for quick reuse.

调用gc方法建议Java虚拟机花力气回收未使用的对象,以便使它们当前占用的内存可以快速重用。

There is no guarantee that the actual GC will be triggered.

不能保证实际的GC会被触发

System.gc() triggers a major GC. Hence, there is a risk of spending some time on the stop-the-world phase, depending on your garbage collector implementation. As a result, we have an unreliable tool with a potentially significant performance penalty.

System.gc() 触发了一个主要的GC。因此,有可能在停止-世界阶段花费一些时间,这取决于你的垃圾收集器实现。因此,我们拥有一个不可靠的工具,而且可能会有很大的性能损失

Existence of explicit garbage collection invocation should be a serious red flag for everyone.

明确的垃圾收集调用的存在对每个人来说都应该是一个严重的红旗。

We can prevent System.gc() from doing any work by using the -XX:DisableExplicitGC JVM flag.

我们可以通过使用-XX:DisableExplicitGCJVM标志来防止System.gc()做任何工作。

3.1. Performance Tuning

3.1.性能调校

It’s worth noting that just before throwing an OutOfMemoryError, the JVM will perform a full GC. Therefore, an explicit call to System.gc() will not save us from failure.

值得注意的是,在抛出OutOfMemoryError之前,JVM将执行一次完整的GC。因此,明确调用System.gc()不会使我们免于失败

Garbage collectors nowadays are really smart. They have all knowledge about memory usage and other statistics to be able to make proper decisions. Hence, we should trust them.

现在的垃圾收集器真的很聪明。它们拥有所有关于内存使用和其他统计数据的知识,能够做出正确的决定。因此,我们应该信任他们。

In case of memory issues, we have a bunch of settings we can change to tune our application — starting from choosing a different garbage collector, through setting desired application time/GC time ratio, and finally, ending with setting fixed sizes for memory segments.

如果出现内存问题,我们有一堆设置可以改变,以调整我们的应用程序 – 从选择不同的垃圾收集器开始,通过设置所需的应用程序时间/GC时间比例,最后,以设置内存段的固定大小结束。

There are also ways to mitigate the effects of Full GC caused by an explicit call. We can use one of the flags:

也有一些方法可以减轻显式调用引起的Full GC的影响。我们可以使用其中一个标志。

-XX:+ExplicitGCInvokesConcurrent

or:

或。

-XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses

If we really want our app to work properly, we should solve the real underlying memory problem.

如果我们真的希望我们的应用程序能够正常工作,我们应该解决真正的潜在内存问题。

In the next chapter, we’ll see a practical example when explicitly calling System.gc() seems to be useful.

在下一章,我们将看到一个实际的例子,当明确调用System.gc()似乎是有用的。

4. Usage Example

4.使用实例

4.1. Scenario

4.1.场景

Let’s write a test app. We want to find a situation when calling System.gc() might be useful.

我们来写一个测试应用程序。我们想找到一个调用System.gc()可能有用的情况

Minor garbage collection happens more often than the major one. So, we should probably focus on the latter. A single object is moved to tenured space if it “survived” a few collections and is still reachable from GC roots.

小的垃圾收集比大的垃圾收集更经常发生。所以,我们也许应该关注后者。如果一个单一的对象在几次收集中 “幸存 “下来,并且仍然可以从GC根部到达,那么它就会被移到永久空间。

Let’s imagine we have a huge collection of objects that are alive for some time. Then, at some point, we’re clearing the collection of objects. Maybe it’s a good moment to run System.gc()?

让我们想象一下,我们有一个巨大的对象集合,这些对象在一段时间内是活的。然后,在某个时刻,我们要清除对象集合。也许这是一个运行System.gc()的好时机?

4.2. Demo Application

4.2.演示应用

We’ll create a simple console app that will allow us to simulate that scenario:

我们将创建一个简单的控制台应用程序,使我们能够模拟这种情况。

public class DemoApplication {

    private static final Map<String, String> cache = new HashMap<String, String>();

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        while (scanner.hasNext()) {
            final String next = scanner.next();
            if ("fill".equals(next)) {
                for (int i = 0; i < 1000000; i++) { 
                    cache.put(randomUUID().toString(), randomUUID().toString()); 
                } 
            } else if ("invalidate".equals(next)) {
                cache.clear();
            } else if ("gc".equals(next)) {
                System.gc();
            } else if ("exit".equals(next)) {
                System.exit(0);
            } else {
                System.out.println("unknown");
            }
        }
    }
}

4.3. Running the Demo

4.3.运行演示

Let’s run our application with a few additional flags:

让我们用一些额外的标志来运行我们的应用程序。

-XX:+PrintGCDetails -Xloggc:gclog.log -Xms100M -Xmx500M -XX:+UseConcMarkSweepGC

The first two flags are needed to log GC information. The next two flags are setting initial heap size and then maximum heap size. We want to keep the heap size low to force GC to be more active. Finally, we’re deciding to use CMS – Concurrent Mark and Sweep garbage collector. It’s time to run our app!

前两个标志是用来记录GC信息的。接下来的两个标志是设置初始堆大小和最大堆大小。我们希望保持较低的堆大小以迫使GC更加活跃。最后,我们决定使用CMS – Concurrent Mark and Sweep垃圾收集器。现在是时候运行我们的应用程序了

First, let’s try to fill tenured space. Type fill.

首先,让我们尝试填补任期的空间。输入fill.

We can investigate our gclog.log file to see what happened. We’ll see around 15 collections. The line logged for single collections looks like:

我们可以调查我们的gclog.log文件,看看发生了什么。我们会看到大约15个集合。单个集合的记录行看起来像。

197.057: [GC (Allocation Failure) 197.057: [ParNew: 67498K->40K(75840K), 0.0016945 secs] 
  168754K->101295K(244192K), 0.0017865 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] secs]

As we can see, the memory is filled.

我们可以看到,内存被填满了。

Next, let’s force System.gc() by typing gc. We can see memory usage didn’t change significantly:

接下来,让我们通过输入gc来强制执行System.gc()。我们可以看到,内存使用量并没有明显变化。

238.810: [Full GC (System.gc()) 238.810: [CMS: 101255K->101231K(168352K); 0.2634318 secs] 
  120693K->101231K(244192K), [Metaspace: 32186K->32186K(1079296K)], 0.2635908 secs] 
  [Times: user=0.27 sys=0.00, real=0.26 secs]

After a few more runs, we’ll see that memory size stays at the same level.

再运行几次后,我们会看到内存大小保持在同一水平。

Let’s clear the cache by typing invalidate. We should see no more log lines appear in the gclog.log file.

让我们通过输入invalidate清除缓存。我们应该看到gclog.log文件中不再有日志行出现。

We can try to fill cache a few more times, but no GC is happening. This is a moment when we can outsmart the garbage collector. Now, after forcing GC, we’ll see a line like:

我们可以再尝试填充缓存几次,但没有GC发生。这是一个我们可以胜过垃圾收集器的时刻。现在,在强制GC之后,我们会看到这样一行字。

262.124: [Full GC (System.gc()) 262.124: [CMS: 101523K->14122K(169324K); 0.0975656 secs] 
  103369K->14122K(245612K), [Metaspace: 32203K->32203K(1079296K)], 0.0977279 secs]
  [Times: user=0.10 sys=0.00, real=0.10 secs]

We’ve released an impressive amount of memory! But was it really necessary right now? What happened?

我们已经释放了令人印象深刻的内存!但是现在真的有必要这样做吗?发生了什么?

According to this example, calling System.gc() might seem tempting when we’re releasing big objects or invalidating caches.

根据这个例子,当我们释放大对象或使缓存失效时,调用System.gc()可能看起来很诱人。

5. Other Usages

5.其他用途

There are very few reasons when an explicit call to the System.gc() method might be useful.

明确调用System.gc()方法可能有用的原因非常少。

One possible reason is cleaning memory after server startup — we’re starting a server or application which does a lot of preparation. After that, there are a lot of objects to be finalized. However, cleaning after such preparation shouldn’t be our responsibility.

一个可能的原因是服务器启动后清理内存–我们正在启动一个做了很多准备工作的服务器或应用程序。在这之后,有很多对象要被最终确定。然而,这种准备工作之后的清理工作不应该是我们的责任。

Another is memory leak analysis — it’s more a debugging practice than something we would like to keep in the production code. Calling System.gc() and seeing heap space still being high might be an indication of a memory leak.

另一个是内存泄漏分析它更像是一种调试实践,而不是我们希望在生产代码中保留的东西。调用System.gc()并看到堆空间仍然很高,可能是内存泄漏的迹象。

6. Summary

6.归纳总结

In this article, we investigated the System.gc() method and when it might seem useful.

在这篇文章中,我们研究了System.gc()方法,以及它在什么时候可能显得有用。

We should never rely on it when it comes to the correctness of our app. GC in most cases is smarter than us, and in case of any memory problems, we should consider tuning the virtual machine instead of making such an explicit call.

当涉及到我们应用程序的正确性时,我们绝不应该依赖它。GC在大多数情况下比我们更聪明,如果出现任何内存问题,我们应该考虑调整虚拟机,而不是进行这样的明确调用。

As usual, the code used in this article can be found over on GitHub.

像往常一样,本文中使用的代码可以在GitHub上找到over