是否可以在 Java 中强制进行垃圾收集,即使这样做很棘手?我知道 System.gc();
和 Runtime.gc();
,但他们只建议进行 GC。我怎样才能强制GC?
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();
}
}
System.gc()
更好的了。清除 WeakReference
与内存回收无关。我个人已经测试过该代码,发现它毫无用处。简单得离谱的代码 System.gc(); System.gc();
的结果要好得多。
System.gc(); System.gc();
的效率进行比较,但知道它是否比这更好用肯定会很有趣。事实上,只打印它调用 System.gc()
的次数就足够了。达到2的机会非常渺茫。
强制 GC 的最佳(如果不是唯一)方法是编写自定义 JVM。我相信垃圾收集器是可插拔的,因此您可能只需选择一个可用的实现并对其进行调整。
注意:这不是一个简单的答案。
使用 Java™ Virtual Machine Tool Interface (JVM TI),函数
jvmtiError ForceGarbageCollection(jvmtiEnv* env)
将“强制 VM 执行垃圾回收”。 JVM TI 是 JavaTM Platform Debugger Architecture (JPDA) 的一部分。
是的,几乎有可能迫使您必须以相同的顺序调用方法,同时这些方法是:
System.gc ();
System.runFinalization ();
即使只是清理一个对象同时使用这两种方法也会强制垃圾收集器使用 finalise()
方法来释放分配的内存并执行 finalize()
方法规定的不可访问对象的方法。
然而,使用垃圾收集器是一种糟糕的做法,因为使用它可能会给软件带来比内存更糟糕的过载,垃圾收集器有自己的线程,无法控制加上取决于gc 使用的算法可能需要更多时间并且被认为非常低效,如果它最糟糕,您应该在 gc 的帮助下检查您的软件,因为它肯定是坏的,一个好的解决方案不能依赖 gc。
注意:请记住,这仅在 finalize 方法中不是对象的重新分配时才有效,如果发生这种情况,对象将保持活动状态并且它将具有技术上可能的复活。
gc()
只是运行垃圾收集的提示。 runFinalizers()
仅对“已发现被丢弃”的对象运行终结器。如果 gc 没有实际运行,则可能没有此类对象...
根据 OutOfMemoryError 的文档,它声明它不会被抛出,除非 VM 在完全垃圾回收后未能回收内存。因此,如果您一直分配内存直到出现错误,您将已经强制进行完整的垃圾回收。
大概你真正想问的问题是“我怎样才能回收我认为应该通过垃圾收集回收的内存?”
您可以从命令行触发 GC。这对于批处理/crontab 很有用:
jdk1.7.0/bin/jcmd <pid> GC.run
看 :
https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/tooldescr006.html
手动请求 GC(不是来自 System.gc()):
Go To : JDK 中的 bin 文件夹 eg.-C:\Program Files\Java\jdk1.6.0_31\bin 打开 jconsole.exe 连接到所需的本地进程。转到内存选项卡并单击执行 GC。
如何强制 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
.gc 是在未来版本中消除的候选 - 一位 Sun 工程师曾经评论说,世界上可能只有不到 20 人真正知道如何使用 .gc() - 我昨晚在中央/关键上做了几个小时的工作使用 SecureRandom 生成数据的数据结构,在刚刚超过 40,000 个对象的某个地方,vm 会减慢速度,就好像它已经用完了指针一样。很明显,它在 16 位指针表上窒息,并表现出经典的“故障机器”行为。
我尝试了 -Xms 等等,一直在旋转,直到它运行到大约 57,xxx 一些东西。然后它会在 gc() 之后运行 gc 从 57,127 到 57,128 - 大约是 Easy Money 营地的代码膨胀速度。
你的设计需要基本的返工,可能是滑动窗口的方法。
gc()
尚未被弃用。弃用/删除会破坏太多现有的应用程序,因此无法认真考虑,IMO。
JVM 规范没有具体说明垃圾收集。因此,供应商可以自由地以他们的方式实现 GC。
所以这种模糊性会导致垃圾收集行为的不确定性。您应该检查您的 JVM 详细信息以了解垃圾收集方法/算法。还有一些选项可以自定义行为。
System.GC
可以保证“做某事”:)
如果您需要强制垃圾收集,也许您应该考虑如何管理资源。您是否正在创建持久存在于内存中的大型对象?您是否正在创建具有 Disposable
接口并且在完成后不调用 dispose()
的大型对象(例如,图形类)?您是否在类级别声明了仅在单个方法中需要的内容?
如果您能描述需要垃圾收集的原因会更好。如果您使用 SWT,您可以释放 Image
和 Font
等资源以释放内存。例如:
Image img = new Image(Display.getDefault(), 16, 16);
img.dispose();
还有一些工具可以确定未处置的资源。
另一种选择是不创建新对象。
对象池是为了减少 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) { }
}
您可以尝试使用 Runtime.getRuntime().gc()
或使用实用方法 System.gc()
注意:这些方法不能确保 GC。它们的范围应该仅限于 JVM,而不是在您的应用程序中以编程方式处理它。
我们可以使用 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()
好
我做了一些实验(参见 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 的创建者如此狂妄自大,以至于认为他们比我们更清楚我们是否想要这样做,或者我们是否应该这样做。不要那么嚣张。如果某些东西像魔术一样起作用,那么必须提供一些绕过魔术的方法。
如果内存不足并获得 OutOfMemoryException
,您可以尝试通过使用 java -Xms128m -Xmx512m
而不是仅 java
启动程序来增加 Java 可用的堆空间量。这将为您提供 128Mb 的初始堆大小和最大 512Mb,这远远超过标准的 32Mb/128Mb。
java -Xms512M -Xmx1024M
真的,我不明白你。但是要清楚“无限对象创建”,我的意思是在我的大系统中有一些代码可以创建可以处理并在内存中存活的对象,我实际上无法获得这段代码,只是手势!
这是正确的,只是手势。您已经有了几张海报已经给出的标准答案。让我们一一来看:
我实际上无法获得这段代码
正确,没有实际的 jvm - 这只是一个规范,一堆描述所需行为的计算机科学......我最近研究了从本机代码初始化 Java 对象。为了得到你想要的,唯一的方法是做所谓的积极归零。如果做错了,那么错误是非常糟糕的,以至于我们不得不将自己限制在问题的原始范围内:
我的大系统中的一些代码创建对象
这里的大多数海报都会假设您说您正在处理一个界面,如果是这样,我们将不得不查看您是一次收到整个对象还是一个项目。
如果您不再需要一个对象,您可以将 null 分配给该对象,但如果您弄错了,则会生成一个空指针异常。我敢打赌,如果您使用 NIO,您可以取得更好的工作
任何时候你、我或其他人得到:“求求你了,我非常需要那个。”它几乎是几乎完全破坏您正在尝试的工作的普遍前兆....给我们写一个小示例代码,从中清除任何实际使用的代码并向我们展示您的问题。
不要沮丧。这通常会解决您的 dba 使用从某处购买的软件包,并且原始设计并未针对大量数据结构进行调整。
这是很常见的。
供参考
方法调用 System.runFinalizersOnExit(true) 保证在 Java 关闭之前调用终结器方法。但是,这种方法本质上是不安全的,并且已被弃用。另一种方法是使用 Runtime.addShutdownHook 方法添加“关闭挂钩”。
马萨拉特·西迪基
有一些间接的方法可以强制垃圾收集器。您只需要用临时对象填充堆,直到垃圾收集器将执行。我已经制作了以这种方式强制垃圾收集器的类:
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 可移植方式。
我想在这里添加一些东西。请注意 Java 在虚拟机而不是实际机器上运行。虚拟机有自己的与机器通信的方式。它可能因系统而异。现在当我们调用 GC 时,我们要求 Java 的虚拟机调用垃圾收集器。
由于垃圾收集器带有虚拟机,我们不能强迫它在那里进行清理。而是我们将我们的请求与垃圾收集器一起排队。这取决于虚拟机,在特定时间后(这可能因系统而异,通常当分配给 JVM 的阈值内存已满时)实际机器将释放空间。 :D
在具有 G1 GC 的 OracleJDK 10 上,对 System.gc()
的一次调用将导致 GC 清理旧集合。我不确定 GC 是否会立即运行。但是,即使在循环中多次调用 System.gc()
,GC 也不会清理 Young Collection。要让 GC 清理 Young Collection,您必须在不调用 System.gc()
的情况下循环分配(例如 new byte[1024]
)。出于某种原因调用 System.gc()
会阻止 GC 清理年轻集合。
如果您使用的是 JUnit 和 Spring,请尝试在每个测试类中添加:
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
non-deterministic == trouble