如何从Shell中强制执行垃圾回收?

135

我正在使用远程jmap查看堆栈,我想在其中强制进行垃圾回收。不使用jvisualvm或jconsole等工具如何实现?

我知道不应该频繁强制进行垃圾回收,而是要找出为什么堆栈会变得如此庞大。

同时我也明白System.GC()并不能真正强制进行垃圾回收,它只是通知垃圾回收器你希望进行回收。

那么是否有一种简单的方法来实现强制进行垃圾回收呢?有没有我遗漏的命令行应用程序?


与https://dev59.com/FXM_5IYBdhLWcg3wNgKY不同。 - Raedwald
12个回答

401
自JDK 7以来,您可以使用JDK命令工具“jcmd”,例如: jcmd <pid> GC.run

35
你们为什么不告诉我这些事情呢?! :) - noahlz
2
如果你遇到了“AttachNotSupportedException: Unable to open socket file”错误,请参考我在这个答案中的补充 - Thomas Rebele
如果你得到了Explicit GC is disabled, no GC has been performed的错误,这可能是由于-XX:+DisableExplicitGC虚拟机参数引起的。参见:http://mail.openjdk.java.net/pipermail/serviceability-dev/2017-August/021763.html - Eyal Roth
这仅适用于Oracle JDK,不适用于open-jdk。 - Ali Saleh
在查找所有Java <pid>之前,请先运行jcmd。 - renedet
2
@AliSaleh:对我来说,它也可以在openjdk(Debian和Ubuntu)上运行。我使用sudo apt install openjdk-11-jdk-headless进行安装,然后使用jcmd 0 GC.run运行(进程ID“0”表示:“对所有Java进程执行”),所有的java进程都分配了更少的RAM(Resident Set Size RSS;请参见此处https://dev59.com/oGsz5IYBdhLWcg3weHkA#21049737)。虚拟内存大小(VSZ)仍然与以前相同(使用`ps -A ups -A u | grep java`进行检查)。 - user14972917

109

如果你运行jmap -histo:live <pid>,那么它会在打印任何内容之前强制对堆进行一次完整的GC。


3
强制所有Java应用程序进行垃圾回收: ps axf | grep java | grep -v grep | awk '{print "jmap -histo:live " $1}'|sh - gtrak
1
那个在哪里有记录?如果需要 -F,没有 :live 怎么办? - nafg
7
来自神秘的2014年未来的问候。jcmd现在是正确的工具。 - noahlz
当运行 "jmap -histo:live <pid>" 命令时,打印结果将包含垃圾回收之前的引用。 - Geker

26

你可以通过免费的jmxterm程序实现此操作。

这样启动:

java -jar jmxterm-1.0-alpha-4-uber.jar

从那里,您可以连接到主机并触发垃圾回收:

$>open host:jmxport
#Connection to host:jmxport is opened
$>bean java.lang:type=Memory
#bean is set to java.lang:type=Memory
$>run gc
#calling operation gc of mbean java.lang:type=Memory
#operation returns: 
null
$>quit
#bye

查看jmxterm网站上的文档,了解将其嵌入bash/perl/ruby/其他脚本中的信息。 我已经在Python中使用popen2或在Perl中使用open3来执行此操作。

更新:这是使用jmxterm的一行命令:

echo run -b java.lang:type=Memory gc | java -jar jmxterm-1.0-alpha-4-uber.jar -n -l host:port

20

除了user3198490的回答之外,还需要注意一点。运行这个命令可能会出现以下错误信息:

$ jcmd 1805 GC.run    
[16:08:01]
1805:
com.sun.tools.attach.AttachNotSupportedException: Unable to open socket file: target process not responding or HotSpot VM not loaded
...

使用这个stackoverflow答案的帮助可以解决这个问题。

sudo -u <process_owner> jcmd <pid> GC.run

其中<process_owner>是使用PID<pid>运行该进程的用户。您可以从tophtop中获取两个值。


以下情况如何?同样的问题吗?java.io.IOException: 操作不允许 - dhockey
我还没有遇到过这个错误信息。也许使用 sudo -u<process_owner> jcmd<pid> GC.run 可以解决,你可以试一下吗? 这个命令应该是安全的。 - Thomas Rebele
我本来想这么做,但是我在那台机器上没有sudo权限。 - dhockey
该工具运行正常。您只是没有操作系统中执行它所需的正确权限。同样适用于其他应用程序,即使不使用Java。 - aled

12

对于 Linux:

$ jcmd $(pgrep java) GC.run

jcmd与JDK捆绑,$(pgrep java)获取java进程的ID


这只适用于似乎只有一个Java进程在运行的情况。否则,它会将第二个PID解释为jcmd的命令,显然无法识别并引发错误。 - Cas

7

还有一些其他的解决方案(这里已经有很多好的解决方案了):

以下示例是针对cmdline-jmxclient的:

$ java -jar cmdline-jmxclient-0.10.3.jar - localhost:3812 'java.lang:type=Memory' gc

这很好,因为只有一行,可以很容易地将其放入脚本中。

1
除了user3198490的答案之外,如果运行jcmd <pid> GC.run后仍然没有任何变化,原因可能是: GC.run本质上调用了java.lang.System.gc(), 这只是给gc的一个提示,JVM可以选择忽略它。
如果您想确保强制进行完整的GC,则可以使用: jcmd <pid> GC.heap_dump filename.hprof 该命令的原始目的是创建一个名为filename.hprof的堆转储文件。但是作为副作用,为了达到所有活动对象,它会 "请求进行完整的GC,除非指定了-all选项".

还有一些其他命令,如jmap -histo:live <PID>此答案中提到,以同样的方式触发GC。


0

如果您正在使用jolokia与您的应用程序,您可以使用此命令触发垃圾回收:

curl http://localhost:8558/jolokia/exec/java.lang:type=Memory/gc

0

我认为没有相应的命令行选项。

你需要使用jvisualvm/jconsole。

我建议你使用这些工具来确定为什么你的程序占用了很多内存。

无论如何,你不应该强制进行垃圾回收,因为这肯定会干扰垃圾回收算法并使你的程序变慢。


我相信在2010年有一种方法可以做到这一点,但即使当时不能做到,在2022年这已经不再是正确的答案了。其中一种方法是使用jcmd命令。 - gortiz

0
考虑使用以下带有jcmd的GNU平行工具来处理多进程: parallel 'jcmd {} GC.run' ::: $(pgrep java)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接