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


当前回答

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

对象池是为了减少Java中对GC的需求。

对象池通常不会比对象创建快(特别是对于轻量级对象),但它比垃圾收集快。如果您创建10,000个对象,每个对象是16个字节。GC需要回收160,000个字节。另一方面,如果您不需要同时使用所有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) { }

}

其他回答

我们可以使用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());
}

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

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

在带有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。

I did some experimentation (see https://github.com/mikenakis/ForcingTheJvmToGarbageCollect) trying about a dozen different ways of performing a garbage collection, including ways described in this answer, and more, and I found that there is absolutely no frigging way to deterministically force the JVM to do a complete garbage collection. Even the best answers to this question are only partially successful in that the best they achieve is some garbage collection, but never a guaranteed full garbage collection.

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

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 );
    }
}

不幸的是,睡眠中的数字10是有魔力的;它假设您每秒执行中等数量的内存分配,这将导致中等数量的结束。如果你通过对象的速度更快,那么10可能是不够的,你可能需要等待更长的时间。您可以将其设置为100,但无论您将其设置为多少,总有可能它不够用。

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

在我看来,Java虚拟机没有提供执行强制的、无条件的、确定的、绝对彻底的、让世界停止的垃圾收集的方法,这一事实使它崩溃了。

换句话说,JVM的创造者是如此傲慢,以至于认为他们比我们更了解我们是否想要这样做,或者我们是否应该这样做。别那么傲慢。如果某些东西像魔法一样起作用,那么必须提供一些绕过魔法的方法。

您可以从命令行触发GC。这对于batch/crontab非常有用:

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

看到的:

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