繁体   English   中英

Java垃圾收集器和内存的问题

[英]Problems with Java garbage collector and memory

我在使用Java应用程序时遇到了一个非常奇怪的问题。

本质上,这是一个使用木兰(cms系统)的网页,在生产环境中有4个实例可用。 有时,在Java进程中,CPU会达到100%。

因此,第一种方法是进行线程转储,并检查有问题的线程,我发现这很奇怪:

"GC task thread#0 (ParallelGC)" prio=10 tid=0x000000000ce37800 nid=0x7dcb runnable 
"GC task thread#1 (ParallelGC)" prio=10 tid=0x000000000ce39000 nid=0x7dcc runnable 

好的,这很奇怪,我从来没有遇到过这样的垃圾收集器问题,所以我们要做的下一件事是激活JMX并使用jvisualvm检查机器:堆内存使用率确实很高(95%)。

天真的方法:增加内存,因此,在重新启动的具有增加的内存(6 GB!)的服务器上出现问题需要花费更多的时间,而在其他具有较少内存(4GB!)的服务器上重新启动后,问题出现了运行10天后,问题仍然需要几天的时间才能重新出现。 另外,我尝试使用服务器失败时的apache访问日志,并使用JMeter尝试将请求重播到本地服务器中以重现该错误...这也不起作用。

然后,我对日志进行了更多调查以发现此错误

info.magnolia.module.data.importer.ImportException: Error while importing with handler [brightcoveplaylist]:GC overhead limit exceeded
at info.magnolia.module.data.importer.ImportHandler.execute(ImportHandler.java:464)
at info.magnolia.module.data.commands.ImportCommand.execute(ImportCommand.java:83)
at info.magnolia.commands.MgnlCommand.executePooledOrSynchronized(MgnlCommand.java:174)
at info.magnolia.commands.MgnlCommand.execute(MgnlCommand.java:161)
at info.magnolia.module.scheduler.CommandJob.execute(CommandJob.java:91)
at org.quartz.core.JobRunShell.run(JobRunShell.java:216)
at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:549)
    Caused by: java.lang.OutOfMemoryError: GC overhead limit exceeded

另一个例子

    Caused by: java.lang.OutOfMemoryError: GC overhead limit exceeded
    at java.util.Arrays.copyOf(Arrays.java:2894)
    at java.lang.AbstractStringBuilder.expandCapacity(AbstractStringBuilder.java:117)
    at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:407)
    at java.lang.StringBuilder.append(StringBuilder.java:136)
    at java.lang.StackTraceElement.toString(StackTraceElement.java:175)
    at java.lang.String.valueOf(String.java:2838)
    at java.lang.StringBuilder.append(StringBuilder.java:132)
    at java.lang.Throwable.printStackTrace(Throwable.java:529)
    at org.apache.log4j.DefaultThrowableRenderer.render(DefaultThrowableRenderer.java:60)
    at org.apache.log4j.spi.ThrowableInformation.getThrowableStrRep(ThrowableInformation.java:87)
    at org.apache.log4j.spi.LoggingEvent.getThrowableStrRep(LoggingEvent.java:413)
    at org.apache.log4j.AsyncAppender.append(AsyncAppender.java:162)
    at org.apache.log4j.AppenderSkeleton.doAppend(AppenderSkeleton.java:251)
    at org.apache.log4j.helpers.AppenderAttachableImpl.appendLoopOnAppenders(AppenderAttachableImpl.java:66)
    at org.apache.log4j.Category.callAppenders(Category.java:206)
    at org.apache.log4j.Category.forcedLog(Category.java:391)
    at org.apache.log4j.Category.log(Category.java:856)
    at org.slf4j.impl.Log4jLoggerAdapter.error(Log4jLoggerAdapter.java:576)
    at info.magnolia.module.templatingkit.functions.STKTemplatingFunctions.getReferencedContent(STKTemplatingFunctions.java:417)
    at info.magnolia.module.templatingkit.templates.components.InternalLinkModel.getLinkNode(InternalLinkModel.java:90)
    at info.magnolia.module.templatingkit.templates.components.InternalLinkModel.getLink(InternalLinkModel.java:66)
    at sun.reflect.GeneratedMethodAccessor174.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:622)
    at freemarker.ext.beans.BeansWrapper.invokeMethod(BeansWrapper.java:866)
    at freemarker.ext.beans.BeanModel.invokeThroughDescriptor(BeanModel.java:277)
    at freemarker.ext.beans.BeanModel.get(BeanModel.java:184)
    at freemarker.core.Dot._getAsTemplateModel(Dot.java:76)
    at freemarker.core.Expression.getAsTemplateModel(Expression.java:89)
    at freemarker.core.BuiltIn$existsBI._getAsTemplateModel(BuiltIn.java:709)
    at freemarker.core.BuiltIn$existsBI.isTrue(BuiltIn.java:720)
    at freemarker.core.OrExpression.isTrue(OrExpression.java:68)

然后我发现这种问题是由于垃圾收集器使用了大量的CPU而无法释放大量内存造成的

好的,这是内存问题在CPU中显示出来,因此,如果解决了内存使用问题,则CPU应该可以正常工作,因此我进行了一个堆转储,不幸的是它太大了无法打开它(文件是10GB),无论如何,我在本地运行服务器,将其加载了一点并进行了堆转储,打开后,我发现了一些有趣的东西:

有很多实例

AbstractReferenceMap$WeakRef  ==> Takes 21.6% of the memory, 9 million instances
AbstractReferenceMap$ReferenceEntry  ==> Takes 9.6% of the memory, 3 million instances

另外,我发现了一个映射,它似乎被用作“缓存”(可怕,但确实如此),问题是这种映射不同步,并且在线程之间共享(是静态的),这个问题不仅可能是并发写入,而且还存在以下事实:由于缺乏同步,因此无法保证线程A将看到线程B对映射所做的更改,但是,我无法弄清楚如何使用内存蚀分析器链接此可疑映射。 ,因为它不使用AbstracReferenceMap,所以它只是一个普通的HashMap。

不幸的是,我们没有直接使用这些类(显然,代码使用了它们,但没有直接使用它们),因此我似乎陷入了死胡同。

我的问题是

  1. 我无法重现错误
  2. 我无法弄清楚内存在哪里泄漏(如果是这种情况)

有什么想法吗?

绝对应该删除“ no-op” finalize()方法,因为它们可能会使任何GC性能问题恶化。 但是我怀疑您还有其他内存泄漏问题。

忠告:

  • 首先摆脱无用的finalize()方法。

  • 如果您还有其他finalize()方法,请考虑摆脱它们。 (依靠终结来做事通常是个坏主意……)

  • 使用内存探查器尝试识别正在泄漏的对象以及造成泄漏的原因。 有很多SO问题...和其他有关查找Java代码泄漏的资源。 例如:


现在到您的特定症状。

首先, OutOfMemoryError的位置可能无关紧要。

但是,事实是您有大量的AbstractReferenceMap$WeakRefAbstractReferenceMap$ReferenceEntry对象,这表明您的应用程序或所使用的库中的某些东西正在进行大量的缓存,这暗示着缓存。问题。 AbstractReferenceMap类是Apache Commons Collections库的一部分。它是ReferenceMapReferenceIdentityMap的超类。)

您需要跟踪那些WeakRefReferenceEntry对象所属的地图对象(一个或多个)以及它们引用的(目标)对象。 然后,您需要弄清楚是什么创建了它们/它们,并弄清了为什么不响应高内存需求而清除条目。

  • 您是否对其他位置的目标对象有很强的引用(这将阻止WeakRefs损坏)?

  • 是否未正确使用地图以导致泄漏。 (仔细阅读javadocs ...)

  • 是否在没有外部同步的情况下由多个线程使用映射? 这可能会导致损坏,有可能表现为大量存储泄漏。


不幸的是,这些仅仅是理论,可能还有其他原因导致这种情况。 实际上,可以想象这根本不是内存泄漏。


最后,您发现堆越大,问题越严重。 对我来说,这仍然与Reference /缓存相关的问题一致。

  • 与常规参考相比, Reference对象在GC上的工作量更大。

  • 当GC需要“破坏” Reference ,它将创建更多工作; 例如处理参考队列。

  • 即使发生这种情况,直到最早的下一个GC周期,仍然无法收集生成的无法访问的对象。

因此,我可以看到充满引用的6Gb堆与4Gb堆相比如何显着增加GC中花费的时间百分比,并且这可能会导致“ GC开销限制”机制更早地发挥作用。

但是我认为这是偶然的症状,而不是根本原因。

遇到困难的调试问题,您需要找到一种方法来重现它。 只有这样,您才能测试实验性更改并确定它们使问题变得更好或更糟。 在这种情况下,我会尝试编写循环,以快速创建和删除服务器连接,创建服务器连接并快速向其发送内存消耗大的请求等。

可以重现它之后,请尝试减小堆大小,以查看是否可以更快地重现它。 但是要这样做,因为一个小的堆可能不会达到“ GC开销限制”,这意味着GC正在花费过多时间(某种程度上来说是98%)来尝试恢复内存。

对于内存泄漏,您需要找出在代码中累积对象引用的位置。 例如,它是否构建所有传入网络请求的映射? 网络搜索https://www.google.com/search?q=how+to+debug+java+memory+leaks显示了许多有关如何调试Java内存泄漏的有用文章,包括有关使用诸如Eclipse Memory Analyzer之类的工具的提示您正在使用的。 搜索特定错误消息https://www.google.com/search?q=GC+overhead+limit+exceeded也很有帮助。

no-op finalize()方法不应导致此问题,但是它们可能会加剧该问题。 finalize()上的文档显示,拥有finalize()方法会强制GC 两次确定实例未引用(在调用finalize()之前和之后)。

因此,一旦可以重现问题,请尝试删除那些no-op finalize()方法,然后查看问题是否需要更长的时间才能重现。

重要的是,内存中有许多AbstractReferenceMap$WeakRef实例。 弱引用的目的是在不强制其保留在内存中的情况下引用该对象。 AbstractReferenceMap是一种Map,可以使键和/或值成为弱引用或软引用。 软引用的目的是尝试将对象保留在内存中,但当内存不足时让GC释放它。)无论如何,内存中的所有那些WeakRef实例可能会加剧该问题,但不应保留引用的Map键/值在内存中。 他们指的是什么? 还有什么是指那些对象?

尝试在源代码中找到泄漏的工具,例如plumbr

有很多可能性,也许您已经探索了其中一些。

绝对是某种内存泄漏。

如果您的服务器上有用户会话,并且当用户不活动超过X分钟/小时时用户会话没有到期或未得到适当处理,您将获得已用内存的累积。

如果您有一个或多个程序生成的内容的映射,并且不清除旧的/不需要的条目的映射,则可以再次获得已用内存的累积。 例如,我曾经考虑添加一个映射来跟踪进程线程,以便用户可以从每个线程中获取信息,直到老板指出在任何时候都不会从映射中删除已完成的线程,因此如果用户保持登录状态在活动中,它们将永远保持住这些线程。

您应该尝试在非生产服务器上进行负载测试,在该服务器上模拟大量用户对应用程序的正常使用。 甚至可能限制服务器的内存甚至比平时低。

祝你好运,内存问题很难追踪。

您说您已经尝试过jvisualvm检查机器。 也许,再试一次,像这样:

  • 这次查看“采样器->内存”选项卡。

  • 它应该告诉您哪些(类型)对象占用最多的内存。

  • 然后找出通常在何处创建和删除此类对象。

  • 很多时候,Java代理插入JVM可能导致“奇怪”错误。 如果您正在运行任何代理(例如jrebel / liverebel,newrelic,jprofiler),请尝试先不运行它们。
  • 当使用非标准参数(-XX)运行JVM时,也会发生奇怪的事情。 已知某些组合会引起问题; 您当前使用哪些参数?
  • 内存泄漏也可能存在于木兰本身中,您是否尝试过搜索“木兰泄漏”? 您是否正在使用任何第三方木兰模块? 如果可能,请尝试禁用/移除它们。

该问题可能仅与您的问题的一部分有关。您可以尝试通过在临时/开发服务器上“重放”访问日志来重现该问题。

如果没有其他工作,如果是我,我将执行以下操作:-尝试在“空”的木兰实例上复制问题(没有我的任何代码)-尝试在“空”的木兰实例上复制问题(没有第三方模块)-尝试升级所有软件(木兰,第三方模块,JVM)-最终尝试使用YourKit运行生产站点并尝试查找泄漏

我的猜测是,您正在运行自动导入,该导入会调用ImportHandler的某些实例。 该处理程序配置为对要更新的所有节点进行备份(我认为这是默认选项),并且由于您的数据类型中可能有很多数据,并且所有这些操作都是在会话中完成的,因此您可以运行记不清。 尝试找出它是哪个导入作业,并为其禁用备份。

1月HTH

看来您的内存泄漏正在从阵列中散发出来。 垃圾收集器在识别从数组中删除的对象实例时遇到了麻烦,因此将不会收集这些对象实例以释放内存。 我的建议是,当您确实从数组中删除对象时,将前一个对象的位置分配为null ,因此垃圾收集器可以意识到它是一个null对象,然后将其删除。 怀疑这将是您的确切问题,但是了解这些情况并检查是否是您的问题总是很高兴的。

当需要删除/清理对象实例时,将对象实例分配为null也很好。 这是因为finalize()方法是粗略而邪恶的,有时垃圾回收器不会调用它。 最好的解决方法是自己调用它(或其他类似方法)。 这样,可以确保垃圾清理已成功执行。 就像约书亚·布洛赫(Joshua Bloch)在他的书中所说:有效Java,第2版,第7项,第27页:避免使用终结器。 “终结者是不可预测的,通常是危险的,通常是不必要的”。 您可以在此处查看此部分。

因为没有显示代码,所以我看不到这些方法中的任何一种是否有用,但是仍然值得了解这些内容。 希望这些技巧对您有所帮助!

如上所述,我将与Magnolia的开发人员联系,但与此同时:

您收到此错误的原因是,GC在运行中收集得很少

如果在垃圾回收上花费了太多时间,则并发收集器将抛出OutOfMemoryError:如果在垃圾回收中花费了总时间的98%以上,而回收的堆少于2%,则将抛出OutOfMemoryError。

由于您无法更改实现,因此我建议以一种运行频率较低的方式更改GC的配置,这样就不太可能失败。

这是一个示例配置,只是为了让您开始使用参数,您必须弄清楚您的最佳选择。 GC的日志可能会对此有所帮助

我的VM参数如下:-Xms = 6G -Xmx = 6G -XX:MaxPermSize = 1G -XX:NewSize = 2G -XX:MaxTenuringThreshold = 8 -XX:SurvivorRatio = 7 -XX:+ UseConcMarkSweepGC -XX:+ CMSClassUnloadingEnabled- XX:+ CMSPermGenSweepingEnabled -XX:CMSInitiatingOccupancyFraction = 60 -XX:+ HeapDumpOnOutOfMemoryError -XX:+ PrintGCDetails -XX:+ PrintGCTimeStamps -XX:+ PrintTenuringDistribution -Xloggc:logs / gc.log

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM