我对Java应用程序有一个非常奇怪的问题。
本质上它是一个使用玉兰(一个cms系统)的网页,在生产环境中有4个实例可用。有时CPU在java进程中达到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!)问题在重新启动后20小时出现,而其他内存较少的服务器(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)
然后我发现such problem is due to the garbage collector using a ton of CPU but not able to free much memory
好的,所以MEMORY的问题在CPU中表现出来,所以如果内存使用问题解决了,那么CPU应该没关系,所以我拿了一个堆转换,不幸的是它太大了,无法打开它(文件是10GB),无论如何我运行服务器本地加载了一点点并且使用了一个堆转储,打开它后,我发现了一些有趣的东西:
有一些TON实例
AbstractReferenceMap$WeakRef ==> Takes 21.6% of the memory, 9 million instances
AbstractReferenceMap$ReferenceEntry ==> Takes 9.6% of the memory, 3 million instances
另外,我发现一个Map似乎被用作“缓存”(可怕但却是真的),问题是这样的地图是不同步的并且它在线程之间共享(是静态的),问题可能不仅是并发写入,而且还有缺乏同步的事实,不能保证线程A将看到线程B对地图所做的更改,但是,我无法弄清楚如何使用以下方法链接此可疑地图内存eclipse分析器,因为它不使用AbstracReferenceMap,它只是一个普通的HashMap。
不幸的是,我们不直接使用这些类(显然代码使用它们,但不是直接使用它们),所以我似乎走到了尽头。
我的问题是
有什么想法吗?
答案 0 :(得分:5)
绝对应该删除'no-op'finalize()
方法,因为它们可能会使任何GC性能问题变得更糟。但我怀疑你还有其他的内存泄漏问题。
建议:
首先摆脱无用的finalize()
方法。
如果您有其他finalize()
方法,请考虑删除它们。 (根据最终确定做事情通常是一个坏主意......)
使用内存分析器尝试识别泄漏的对象以及导致泄漏的原因。有很多SO问题......以及其他有关在Java代码中查找泄漏的资源。例如:
现在针对您的特殊症状。
首先,抛出OutOfMemoryError
s的地方可能无关紧要。
但是,您拥有大量AbstractReferenceMap$WeakRef
和AbstractReferenceMap$ReferenceEntry
个对象的事实是一个字符串指示,表明您的应用程序或它正在使用的库正在进行大量的缓存......并且缓存与问题有关。 (AbstractReferenceMap
类是Apache Commons Collections库的一部分。它是ReferenceMap
和ReferenceIdentityMap
的超类。)
您需要跟踪那些WeakRef
和ReferenceEntry
对象所属的地图对象(或对象)以及它们引用的(目标)对象。然后你需要找出创建它/它们的原因,并找出为什么没有清除条目以响应高内存需求。
你是否对其他地方的目标对象有强烈的引用(这会阻止WeakRefs被破坏)?
是否正在使用错误的地图以引起泄漏。 (仔细阅读javadocs ......)
多个线程是否使用了地图而没有外部同步?这可能导致腐败,这可能表现为大量存储泄漏。
不幸的是,这些只是理论,可能还有其他因素导致这种情况。事实上,可以想象这根本不是内存泄漏。
最后,你观察到当堆更大时问题更严重。对我来说,这仍然与Reference
/缓存相关的问题一致。
Reference
个对象比常规引用更适合GC。
当GC需要“打破”Reference
时,会产生更多的工作;例如处理参考队列。
即使发生这种情况,最早在下一个GC循环之前仍然无法收集生成的无法到达的对象。
所以我可以看到一个充满引用的6Gb堆如何显着增加GC中花费的时间百分比......与4Gb堆相比,这可能会导致“GC Overhead Limit”机制提前启动。
但我认为这是一个偶然的症状,而不是根本原因。
答案 1 :(得分:3)
由于调试困难,您需要找到一种方法来重现它。只有这样,您才能测试实验性变化并确定它们是否会使问题变得更好或更糟。在这种情况下,我会尝试编写快速创建&删除服务器连接,创建服务器连接并快速发送内存昂贵的请求等。
在重现之后,尝试减小堆大小以查看是否可以更快地重现它。但这样做是因为小堆可能没有达到“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也很有帮助。
无操作finalize()
方法不应该导致这个问题,但它们可能会加剧它。 finalize()上的文档显示,使用finalize()
方法强制GC 两次确定实例未被引用(在调用finalize()
之前和之后)。
因此,一旦您可以重现该问题,请尝试删除这些无操作finalize()
方法,并查看问题是否需要更长时间才能重现。
内存中有很多AbstractReferenceMap$WeakRef
个实例,这很重要。 弱引用的要点是引用一个对象而不强制它留在内存中。 AbstractReferenceMap是一个Map,可以让键和/或值成为弱引用或软引用。 (软引用的目的是尝试将对象保留在内存中,但是当内存变低时让GC释放它。)无论如何,内存中所有那些WeakRef实例可能会加剧问题但是不应该不要将引用的Map键/值保留在内存中。他们指的是什么?还有什么指的是那些对象?
答案 2 :(得分:3)
尝试使用工具查找源代码中的泄漏,例如plumbr
答案 3 :(得分:2)
有许多可能性,或许你已经探索过其中一些。
这绝对是某种内存泄漏。
如果您的服务器具有用户会话,并且当用户处于非活动状态超过X分钟/小时时,您的用户会话未到期或处理不当,您将获得已用内存的累积。
如果您有一个或多个程序生成的某些地图,并且您没有清除旧/不需要的条目的地图,您可能会再次获得已用内存的累积。例如,我曾经考虑添加一个地图来跟踪进程线程,以便用户可以从每个线程获取信息,直到我的老板指出在任何时候都没有完成线程从地图中删除,所以如果用户保持记录在活跃的时候,他们会永远坚持这些线索。
您应该尝试在非生产服务器上进行负载测试,您可以在其中模拟大量用户对应用的正常使用情况。甚至可能甚至比平时更低限制服务器的内存。
祝你好运,记忆问题很难找到。答案 4 :(得分:1)
你说你已经尝试过jvisualvm来检查机器。也许,再试一次,就像这样:
这次查看“Sampler - > Memory”标签。
它应该告诉你哪些(类型)对象占用的内存最多。
然后找出通常创建和删除此类对象的位置。
答案 5 :(得分:1)
问题可能只与您的一部分有关您可以尝试通过“重播”登台/开发服务器上的访问日志来重现问题。
如果没有其他工作,如果是我,我会做以下事情: - 尝试在“空”Magnolia实例上复制问题(没有我的任何代码) - 尝试在“空”Magnolia实例上复制问题(没有第三方模块) - 尝试升级所有软件(玉兰,第三方模块,JVM) - 最后尝试使用YourKit运行生产站点并尝试找到泄漏
答案 6 :(得分:0)
我的猜测是你有自动导入运行,它会调用一些ImportHandler实例。该处理程序配置为备份它将要更新的所有节点(我认为这是默认选项),并且因为您的数据类型中可能有很多数据,并且因为所有这些都是在会话中完成的内存不足。尝试找出它是哪个导入作业并禁用它的备份。
HTH, 扬
答案 7 :(得分:0)
您的内存泄漏似乎来自您的阵列。垃圾收集器无法识别从阵列中删除的对象实例,因此不会收集以释放内存。我的建议是当你从数组中删除一个对象时,将前一个对象的位置分配给null
,因此垃圾收集器可以意识到它是一个null
对象,并将其删除。怀疑这将是你的确切问题,但知道这些事情总是好的,并检查这是否是你的问题。
当您需要删除/清理它时,将对象实例分配给null
也是很好的。这是因为finalize()
方法是粗略和邪恶的,有时不会被垃圾收集器调用。最好的解决方法是自己调用它(或其他类似的方法)。这样,您可以确保已成功执行垃圾清理。正如Joshua Bloch在他的书中所说:Effective Java,第2版,第7项,第27页:避免使用终结器。 “终结器是不可预测的,通常是危险的,通常是不必要的”。您可以看到here部分。
因为没有显示代码,所以我看不出这些方法是否有用,但仍然值得了解这些方法。希望这些技巧可以帮到你!
答案 8 :(得分:0)
如上所述,我会与Magnolia的开发者联系,但同时:
您收到此错误,因为GC在运行中收集不多
如果太多,并发收集器将抛出OutOfMemoryError 在垃圾收集中花费的时间:如果超过98%的 垃圾收集所花费的总时间少于堆的2% 恢复后,将抛出OutOfMemoryError。
由于您无法更改实施,我建议以较少运行的方式更改GC的配置,以便以这种方式失败的可能性更小。
这是一个示例配置,只是为了让你开始参数,你必须弄清楚你的最佳位置。 GC的日志可能对此有所帮助
我的VM参数如下: -Xms = 6G -Xmx = 6G -XX:MaxPermSize参数= 1G -XX:新尺寸= 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:原木/ gc.log