Java 垃圾回收和年轻代大小

标签 java performance garbage-collection

我最近遇到了性能问题,在 tomcat 上运行的 Java Web 应用程序将挂起一小段时间,导致流量积压,这可能导致 Web 应用程序在几分钟内不可用,我怀疑这与垃圾收集有关。

我是垃圾收集新手,因此需要一些帮助。

我启用了并发标记扫描垃圾收集器,希望这能够消除暂停,但我还没有发现这是否已经解决了问题。

我还同时启用了详细 GC 日志记录。

当前的java选项如下

-XX:MaxPermSize=128m -XX:+CMSClassUnloadingEnabled -XX:+UseConcMarkSweepGC -Xms4g -Xmx4g -Xss256k -verbose:gc -XX:+PrintGCDetails

通过检查 GC 输出,我注意到年轻代空间非常低,为 243MB,并且很快就会耗尽,在检查一段时间的输出时,我在 10 秒内数出了 23 个年轻代回收。

与此同时,总堆消耗稳步上升,达到接近最大值,然后进行完整的垃圾回收,将其从大约 3.5GB 减少到 260MB,然后该模式再次重复。

完整 GC 的示例输出

[GC [ParNew: 232750K->12960K(249216K), 0.0160890 secs] 3836696K->3616934K(4166656K), 0.0162110 secs] [Times: user=0.12 sys=0.01, real=0.02 secs] 
[GC [ParNew: 234528K->11391K(249216K), 0.0127970 secs] 3838502K->3615402K(4166656K), 0.0129370 secs] [Times: user=0.12 sys=0.00, real=0.01 secs] 
[GC [ParNew: 232959K->10253K(249216K), 0.0097850 secs] 3836970K->3614841K(4166656K), 0.0098850 secs] [Times: user=0.11 sys=0.00, real=0.01 secs] 
[GC [1 CMS-initial-mark: 3604588K(3917440K)] 3615964K(4166656K), 0.0096810 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 
[CMS-concurrent-mark: 0.196/0.196 secs] [Times: user=1.44 sys=0.03, real=0.20 secs] 
[CMS-concurrent-preclean: 0.013/0.014 secs] [Times: user=0.04 sys=0.00, real=0.01 secs] 
[GC [ParNew: 231821K->6718K(249216K), 0.0090430 secs] 3836409K->3611789K(4166656K), 0.0091460 secs] [Times: user=0.08 sys=0.01, real=0.01 secs] 
[CMS-concurrent-abortable-preclean: 0.176/0.390 secs] [Times: user=0.97 sys=0.04, real=0.39 secs] 
[GC[YG occupancy: 124723 K (249216 K)][Rescan (parallel) , 0.0698120 secs][weak refs processing, 0.0038070 secs][class unloading, 0.0170180 secs][scrub symbol & string tables, 0.0098050 secs] [1 CMS-remark: 3605071K(3917440K)] 3729794K(4166656K), 0.1070920 secs] [Times: user=0.78 sys=0.02, real=0.11 secs] 
[GC [ParNew: 228286K->6428K(249216K), 0.0079910 secs] 3755282K->3534155K(4166656K), 0.0080720 secs] [Times: user=0.07 sys=0.00, real=0.01 secs] 
[GC [ParNew: 227996K->6880K(249216K), 0.0085010 secs] 3332282K->3111216K(4166656K), 0.0085990 secs] [Times: user=0.08 sys=0.00, real=0.01 secs] 
[GC [ParNew: 228448K->12440K(249216K), 0.0108230 secs] 2721177K->2505200K(4166656K), 0.0109290 secs] [Times: user=0.13 sys=0.00, real=0.01 secs] 
[GC [ParNew: 234008K->8251K(249216K), 0.0073110 secs] 2358432K->2132792K(4166656K), 0.0074120 secs] [Times: user=0.07 sys=0.00, real=0.00 secs] 
[GC [ParNew: 229819K->5170K(249216K), 0.0071920 secs] 2056138K->1831867K(4166656K), 0.0072880 secs] [Times: user=0.07 sys=0.01, real=0.01 secs] 
[GC [ParNew: 226738K->11119K(249216K), 0.0106230 secs] 1589903K->1374447K(4166656K), 0.0107180 secs] [Times: user=0.11 sys=0.00, real=0.01 secs] 
[GC [ParNew: 232687K->8624K(249216K), 0.0078450 secs] 1273082K->1049051K(4166656K), 0.0079440 secs] [Times: user=0.09 sys=0.00, real=0.01 secs] 
[GC [ParNew: 230192K->10130K(249216K), 0.0083440 secs] 733461K->513411K(4166656K), 0.0084420 secs] [Times: user=0.11 sys=0.00, real=0.01 secs] 
[GC [ParNew: 231698K->10655K(249216K), 0.0092440 secs] 544833K->323816K(4166656K), 0.0093450 secs] [Times: user=0.11 sys=0.00, real=0.01 secs] 
[CMS-concurrent-sweep: 4.481/4.569 secs] [Times: user=13.24 sys=0.49, real=4.57 secs] 
[CMS-concurrent-reset: 0.008/0.008 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 
[GC [ParNew: 232223K->9791K(249216K), 0.0095050 secs] 495665K->273758K(4166656K), 0.0096020 secs] [Times: user=0.11 sys=0.00, real=0.01 secs] 
[GC [ParNew: 231359K->7434K(249216K), 0.0080890 secs] 495326K->271660K(4166656K), 0.0082230 secs] [Times: user=0.09 sys=0.00, real=0.01 secs] 
[GC [ParNew: 229002K->5732K(249216K), 0.0053690 secs] 493228K->269993K(4166656K), 0.0054630 secs] [Times: user=0.06 sys=0.00, real=0.01 secs] 
[GC [ParNew: 227300K->4017K(249216K), 0.0060080 secs] 491561K->268433K(4166656K), 0.0061010 secs] [Times: user=0.07 sys=0.00, real=0.00 secs] 

我想了解这种模式是否正常以及我可以做些什么来优化和改进这个C。

我读过有关增加年轻代大小的内容,但不太熟悉垃圾收集,我不确定这是否是正确的方法。

最佳答案

这看起来确实像你的年轻代大小太小 - 频繁的收集并不是真正的问题(这只是意味着你有一个内存密集型程序),但是你有很多内存被提升到下一代。一些事情:

  1. 您有可以汇集的资源吗?您可以使用 ThreadPoolExecutor 而不是创建新的 Thread 对象,或者您可以池化数据库连接吗?这将减慢您的内存消耗 - 池化资源将保留在您的成熟空间中,您无需在年轻空间中不断重新分配它们。

  2. 如果这不是一个选择,或者如果这不能减少成熟空间的消耗,那么就增加年轻代的大小。这样做的目的不是减少年轻代集合的数量(如果将年轻代大小加倍,那么集合数量将减半,但每个集合的成本可能是原来的两倍**,因此您没有获得任何 yield ) ,更确切地说,这样做的目的是让年轻的物体有更多的时间超出范围,这样它们就不会被提升到成熟的空间。您想要进行的比较是完整收集的频率 - 如果在增加年轻代大小后您的完整收集较少,那么您就成功了,否则将年轻代大小减少回默认值。

** 这并不完全正确,因为年轻代收集器是一个复制收集器 - 它将 Activity 对象复制到成熟空间,然后清除年轻空间。这意味着收集器的运行时间与 Activity 对象的数量成正比,而不是与对象总数成正比(就像标记-清除收集的情况一样)。理想情况下,通过增加年轻代的大小,您将减少 Activity 对象的数量,加快收集时间,并减少成熟空间的消耗。

关于Java 垃圾回收和年轻代大小,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16088818/

相关文章:

java - 从另一个项目加载 AnnotationConfigApplicationContext[Eclipse]

java - 为什么我的 HTTP 消息在正文大小达到 Content-Length header 中规定的大小之前就结束了?

python - 有效地创建具有增量值的新列

ios - 使用 SDWebImage 将本地镜像从文档目录加载到 TableView

javascript - 测量四个相似 Javascript 函数之间的 CPU 负载差异

android - Xamarin.Android - GC 停止我的应用程序 2 秒

java - 我们可以关闭终结器吗?

java - 避免 Android 多次垃圾收集调用的高效循环

java - 处理 jdbc 连接时 try/catch 的最佳方法

java - 为什么 Gson 将一个 LinkedListMultimap map View 序列化为 null?