当前位置: 首页 > 面试题库 >

跟踪Java中的内存泄漏/垃圾回收问题

羊舌勇
2023-03-14
问题内容

这是我几个月来一直试图寻找的问题。我有一个运行的Java应用程序,它处理xml提要并将结果存储在数据库中。存在间歇性的资源问题,很难追踪。

背景: 在生产包装盒上(问题最明显的地方),我对包装盒的访问不是特别好,并且无法使Jprofiler运行。那个盒子是一个运行centos5.2,tomcat6和java 1.6.0.11的64位四核8GB计算机。它以这些java-opts开头

JAVA_OPTS="-server -Xmx5g -Xms4g -Xss256k -XX:MaxPermSize=256m -XX:+PrintGCDetails -
XX:+PrintGCTimeStamps -XX:+UseConcMarkSweepGC -XX:+PrintTenuringDistribution -XX:+UseParNewGC"

技术堆栈如下:

  • Centos 64位5.2
  • Java 6u11
  • Male cat6
  • Spring / WebMVC 2.5
  • Hibernate3
  • quartz1.6.1
  • DBCP 1.2.1
  • MySQL 5.0.45
  • Cache1.5.0
  • (当然还有许多其他依赖项,尤其是jakarta-commons库)

我最能重现此问题的是内存要求较低的32位计算机。我确实可以控制。我已经使用JProfiler对其进行了探究,并修复了许多性能问题(同步问题,预编译/缓存xpath查询,减少线程池,删除不必要的休眠预取以及在处理过程中过分的“缓存变暖”)。

在每种情况下,探查器都显示这些资源由于某种原因占用了大量资源,并且一旦进行更改,这些资源就不再是主要的资源消耗。

问题:
JVM似乎完全忽略了内存使用设置,填满了所有内存并且变得无响应。这对于面对最终客户的客户来说是个问题,他们希望定期进行轮询(每5分钟一次,然后重试1分钟),对于我们的运营团队来说,这是不断得到通知的,盒子已变得没有响应,必须重新启动它。此框上没有其他可运行的东西。

问题 似乎
是垃圾回收。我们使用ConcurrentMarkSweep(如上所述)收集器是因为原始的STW收集器导致JDBC超时并变得越来越慢。日志显示,随着内存使用量的增加,即开始引发cms故障,并踢回原始的世界停止收集器,然后该收集器似乎未正确收集。

但是,使用jprofiler运行时,“运行GC”按钮似乎可以很好地清理内存,而不是显示增加的占用空间,但是由于我无法将jprofiler直接连接到生产盒,并且无法解决公认的热点问题,因此我正在使用剩下的是调整垃圾收集盲目的巫毒。

我尝试过的

  • 分析和修复热点。
  • 使用STW,Parallel和CMS垃圾收集器。
  • 以最小/最大堆大小以1 / 2、2 / 4、4 / 5、6 / 6增量运行。
  • 以256M的permgen空间运行时,增量可达1Gb。
  • 以上的许多组合。
  • 我还参考了JVM [调整参考](http://java.sun.com/javase/technologies/hotspot/gc/gc_tuning_6.html),但实际上找不到解释此行为的任何信息或_which_调整的任何示例在这种情况下使用的参数。
  • 我也(未成功)在脱机模式下尝试了jprofiler,并与jconsole,visualvm连接,但是我似乎找不到任何能够影响我的gc日志数据的东西。

不幸的是,该问题也偶尔出现,它似乎是无法预测的,它可以运行几天甚至一周,而不会出现任何问题,或者一天可能失败40次,而我唯一能持续发现的问题就是垃圾收集正在起作用。

任何人都可以针对以下问题提供任何建议:
a)为什么将JVM配置为最大不足6时,它使用8个物理演出和2 gb的交换空间
。b)实际解释或给出合理示例的GC调整参考与高级集合一起使用的时间和种类设置。
c)对最常见的Java内存泄漏的引用(我理解无人认领的引用,但我的意思是在库/框架级别,或者数据结构中更inherenet的内容,例如哈希映射)。

感谢您提供的所有见解。

编辑
Emil H:
1)是的,我的开发集群是生产数据的镜像,一直到媒体服务器。主要区别在于32/64位和可用的RAM数量,我无法轻易复制它们,但是代码,查询和设置是相同的。

2)有一些依赖JaxB的旧代码,但是在重新安排作业顺序以避免调度冲突时,由于每天运行一次,因此通常无需执行。主解析器使用XPath查询,这些查询调用java.xml.xpath包。这是一些热点的来源,其中一个查询未预先编译,而两个查询的引用位于硬编码字符串中。我创建了一个线程安全的缓存(hashmap),并将对xpath查询的引用分解为最终的静态字符串,从而显着降低了资源消耗。查询仍然是处理的很大一部分,但这应该是因为这是应用程序的主要职责。

3)另外,另一个主要使用者是来自JAI的图像操作(重新处理来自提要的图像)。我不熟悉Java的图形库,但是从我发现它们并不是特别泄漏。

(感谢您到目前为止的回答,伙计们!)

更新:
我能够使用VisualVM连接到生产实例,但是它禁用了GC可视化/ run-
GC选项(尽管我可以在本地查看它)。有趣的是:VM的堆分配遵循JAVA_OPTS,并且实际分配的堆处于1-1.5
gigs的舒适水平,并且似乎没有泄漏,但是盒级监视仍然显示出泄漏模式,但是未反映在VM监控中。这个盒子上没有其他东西在运行,所以我很困惑。


问题答案:

好吧,我终于找到了导致此问题的原因,并且我发布了详细的答案,以防其他人遇到这些问题。

我在进程运行时尝试了jmap,但这通常会使jvm进一步挂起,因此我必须使用–
force来运行它。这导致堆转储似乎丢失了很多数据,或者至少丢失了它们之间的引用。为了进行分析,我尝试了jhat,它提供了很多数据,但是解释方式却不多。其次,我尝试了基于eclipse的内存分析工具(http://www.eclipse.org/mat/),该工具表明堆主要是与tomcat相关的类。

问题是jmap没有报告应用程序的实际状态,而只是在关闭时捕获了类,其中大多数是tomcat类。

我尝试了几次,发现模型对象的数量非常多(实际上是数据库中标记为公共对象的数量的2-3倍)。

使用此工具,我分析了慢查询日志以及一些不相关的性能问题。我尝试了超延迟加载(http://docs.jboss.org/hibernate/core/3.3/reference/en/html/performance.html),以及用直接的jdbc查询替换了一些休眠操作(主要是在正在处理大型集合的加载和操作-
替换jdbc只能直接在联接表上使用),并替换了mysql正在记录的其他一些效率低下的查询。

这些步骤改善了前端性能,但仍未解决泄漏问题,该应用程序仍不稳定且操作异常。

最后,我找到了以下选项:-XX:+
HeapDumpOnOutOfMemoryError。最终产生了一个非常大的(〜6.5GB)hprof文件,该文件可以准确显示应用程序的状态。具有讽刺意味的是,该文件是如此之大,以致jhat无法对其进行分析,即使是在装有16gb
ram的盒子上也是如此。幸运的是,MAT能够生成一些漂亮的图形并显示出一些更好的数据。

这次伸出来的是一条石英线程占用了6GB堆中的4.5GB,其中大部分是休眠的StatefulPersistenceContext(https://www.hibernate.org/hib_docs/v3/api/org/hibernate
/engine/StatefulPersistenceContext.html)。此类在内部由hibernate用作其主缓存(我已禁用了由EHCache支持的第二级和查询缓存)。

此类用于启用休眠的大多数功能,因此不能直接禁用它(可以直接解决它,但是spring不支持无状态会话),如果它具有这样的特性,我将感到非常惊讶成熟产品中的主要内存泄漏。那么为什么现在泄漏呢?

好吧,这是综合的:石英线程池实例化为某些东西,它们是threadLocal,spring正在注入一个会话工厂,这是在石英线程生命周期开始时创建一个会话,然后被重用于运行该线程。使用休眠会话的各种石英作业。然后,Hibernate在会话中进行缓存,这是其预期的行为。

然后的问题是线程池从不释放会话,因此休眠处于驻留状态并在会话的生命周期内维护高速缓存。由于这是使用Springs
Hibernate模板支持的,因此没有显式使用会话(我们使用的是dao->管理器->驱动程序->石英作业层次结构,该dao通过spring注入了休眠配置,因此操作是直接在模板上完成)。

因此,会话永远不会关闭,休眠状态是维护对缓存对象的引用,因此它们永远不会被垃圾回收,因此每次运行新作业时,它只会继续填充线程本地的缓存,因此甚至没有不同工作之间的任何共享。另外,由于这是一项写密集型工作(很少读取),因此缓存大部分被浪费了,因此对象不断被创建。

解决方案:创建一个dao方法,该方法显式调用session.flush()和session.clear(),然后在每个作业开始时调用该方法。

该应用程序已经运行了几天,没有任何监控问题,内存错误或重启。

感谢大家对此的帮助,这是一个非常棘手的错误,因为所有事情都按照预期的方式进行了跟踪,但是最后通过3行方法成功解决了所有问题。



 类似资料:
  • 在面对中的一些内存泄漏后,我想知道中是否存在内存泄漏。快速搜索将我带到了以下stackoverflow线程。 @david wasser对此帖子的评论如下: 对象a- 在本例中,ObjectA是代码中正在使用的对象。然而,ObjectA包含一个对ObjectB的引用,该引用实际上是死的(即:ObjectB已经分配和使用,从程序员的角度来看,现在是死的),但程序员忘记了将ObjectA中的引用设置为

  • 如果我有一个垃圾收集器来跟踪分配的每个对象,并在它们不再有对它们的可用引用时立即释放它们,你还会有内存泄漏吗? 考虑到内存泄漏是指没有任何引用的分配,这不是不可能的吗?还是我遗漏了什么? 编辑:所以我认为内存泄漏是您在代码中不再引用的分配。您仍然可以引用的大量累积分配不是我在这里考虑的泄漏。 我也只是在谈论普通的G.C.,已经有一段时间了,但我知道像循环引用这样的案例不会把他们绊倒。我不需要任何语

  • 问题内容: 我正在读取一个很大的文件,并从每一行中提取文本的一小部分。但是,在操作结束时,我的工作记忆很少。似乎垃圾收集器在读取文件后无法释放内存。 我的问题是:有什么办法释放这种记忆?还是这是JVM错误? 我创建了一个SSCCE来演示这一点。它读取一个1 mb(由于16位编码,在Java中为2 mb)的文件,并从每行中提取一个字符(约4000行,因此大约为8 kb)。测试结束时,仍将使用全部2

  • 问题内容: 我们有一个App Engine应用程序,可将许多较大文件写入Google Cloud Store。这些文件是动态创建的CSV文件,因此我们将Python用作缓冲区和写入该缓冲区的接口。 通常,我们的过程如下所示: 据我们了解,它们本身不需要关闭。而是,仅上述内容和需要被关闭。 我们在由App Engine的任务队列调用的中运行上述过程。最终,在几次调用我们的任务后,我们得到以下错误:

  • 主要内容:1 什么是Java 垃圾回收,2 Java 垃圾回收的优势,3 如何取消对象引用,4 finalize()方法,5 gc()方法,6 Java 垃圾回收的例子1 什么是Java 垃圾回收 在Java中,垃圾意味着未引用的对象。 垃圾回收是自动回收运行时未使用的内存的过程。换句话说,这是销毁未使用对象的一种方法。 我们在C语言中使用free() 函数,在C ++中使用delete()。但是,在Java中它是自动执行的。因此,java提供了更好的内存管理。 2 Java 垃圾回收的优势 它

  • 问题内容: 我想知道Java中发生的垃圾回收。它真的能够处理所有未使用的对象并释放最大可能的内存吗? 我还想知道Java垃圾收集与另一种语言(例如C#)相比如何?然后,如何自动垃圾收集与从像C这样的语言中进行手动收集相比又能达到更好的效果呢? 问题答案: 是的,这就是垃圾收集的重点。 有许多不同形式的垃圾收集。如果不增强算法,最简单的形式即引用计数就无法处理某些类型的垃圾(循环引用)。 Java(