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

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

398639 次浏览

你最好的选择是调用System.gc(),这只是一个提示垃圾收集器,你想让它做一个收集。没有办法和立即收集,尽管垃圾收集器是不确定的。

如果您能描述一下需要垃圾回收的原因就更好了。如果你正在使用SWT,你可以释放诸如ImageFont这样的资源来释放内存。例如:

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

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

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

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

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

在未来的版本中,.gc可能会被淘汰——一位Sun工程师曾经评论说,世界上可能只有不到20人真正知道如何使用.gc()——我昨晚花了几个小时研究一个中心/关键数据结构,使用securerrandom生成的数据,当超过40000个对象时,虚拟机就会变慢,就像指针用光了一样。显然,它被16位指针表所阻塞,并表现出典型的“机械故障”行为。

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

你的设计需要基本的重新工作,可能是一个滑动窗口的方法。

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

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

真的,我不懂你。而是成为 清楚“无限物体创造” 我是说有一些 代码在我的大系统做创建 可以处理的对象,并在其中生活 回忆,我无法得到这一片 其实是代码,只是个手势!!< / p >

这是正确的,唯一的姿态。你已经有了很多标准答案。让我们一个一个来看:

    我不能得到这段代码 其实李< / >

正确,没有真正的jvm——这只是一个规范,一堆描述期望行为的计算机科学…我最近研究了如何从本地代码初始化Java对象。要得到你想要的东西,唯一的方法就是执行所谓的主动置空。如果做错了,错误是非常糟糕的,我们必须将自己限制在问题的原始范围内:

  1. 一些代码在我的大系统 创建对象

这里的大多数海报会假设你说你正在处理一个界面,如果是这样的话,我们就必须看看你是一次被交付整个对象还是一个项目。

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

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

不要沮丧。通常情况下,这意味着您的dba正在使用从某个地方购买的包,并且原始设计没有针对大规模数据结构进行调整。

这很常见。

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

Jlibs库有一个很好的垃圾收集实用程序类。你可以使用WeakReference引用对象的一个漂亮的小技巧强制垃圾收集。

RuntimeUtil.gc () from jlibs:

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

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

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

仅供参考

方法调用System.runFinalizersOnExit(true)保证终结器方法 在Java关闭之前调用。然而,这种方法本质上是不安全的 并且已经被弃用。另一种方法是在方法中添加“关机钩子” Runtime.addShutdownHook . < / p >

Masarrat西迪基

使用Java™虚拟机工具接口(JVM TI)函数

jvmtiError ForceGarbageCollection(jvmtiEnv* env)

将“强制虚拟机执行垃圾收集。”JVM TI是JavaTM平台调试器体系结构(JPDA)的一部分。

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

  1. 转到:bin文件夹在JDK中,例如。c: \ Program Files \ Java \ jdk1.6.0_31 \ bin
  2. 打开jconsole.exe
  3. 连接到所需的本地进程。
  4. 进入“内存”页签,单击“执行GC”。

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

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

看到的:

是的几乎可以强制你必须以相同的顺序调用方法,同时这些方法是:

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

即使只有一个对象来清理,同时使用这两个方法强制垃圾收集器使用unreachable对象的finalise()方法来释放分配的内存并执行finalize()方法所述的操作。

然而是一个可怕的实践使用垃圾收集器,因为使用它可能会引入一个加载的软件可能甚至比在内存坏,垃圾收集器有自己的线程是不可能控制+根据gc可能需要更多的时间和所使用的算法是考虑非常低效,您应该检查您的软件如果它坏在gc的帮助,因为它绝对是坏,一个好的解决方案不能依赖于gc。

注意:只是要记住,只有在finalize方法中没有对对象进行重赋时才会工作,如果发生这种情况,对象将保持存活,它将有一个复活,这在技术上是可能的。

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

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的可移植方式。

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

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

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

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

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


}

如果您正在使用JUnit和Spring,请尝试在每个测试类中添加以下内容:

@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)

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

如何强制Java GC

好了,这里有一些不同的 Java GC的方法。

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

enter image description here

这些都不行

这是一个肮脏的小秘密。这些方法都不能保证有效。你真的不能强制Java GC

Java垃圾收集算法是不确定的,虽然所有这些方法都可以激励JVM执行GC,但实际上不能强制执行。如果JVM有太多事情要做,而停止世界操作是不可能的,那么这些命令要么会出错,要么会运行,但GC实际上不会发生。

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


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

解决恼人的问题

如果遇到内存泄漏或对象分配问题,请修复它。你的手指坐在Java任务控制的Force Java GC按钮上,只会拖延时间。使用爪哇飞行记录仪配置应用程序,在VisualVM或JMC中查看结果,并修复问题。尝试强制Java GC是一个愚蠢的游戏。

enter image description here

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

我做了一些实验(见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的创造者是如此傲慢,以至于认为他们比我们更了解我们是否想要这样做,或者我们是否应该这样做。别那么傲慢。如果某些东西像魔法一样起作用,那么必须提供一些绕过魔法的方法。

我想强制gc,因为当它发生时,我的代码被冻结了很长时间。其目的是通过定期引起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();