Java进程内存无限增长。内存泄漏?

13

我们有一个在Solaris 10上运行的Java进程,为约200-300个并发用户提供服务。管理员报告说,进程使用的内存随着时间的推移显著增加。几天后它就会达到2GB,而且不断增长。

我们已经转储了堆,并使用Eclipse Memory Profiler进行了分析,但未能发现任何异常情况。堆大小非常小。

在将内存状态日志添加到我们的应用程序后,我们发现管理员使用的“top”实用程序报告的内存使用情况与MemoryMXBean和Runtime库报告的使用情况存在差异。

下面是两者的输出。

Memory usage information 

From the Runtime library
Free memory: 381MB
Allocated memory: 74MB
Max memory: 456MB
Total free memory: 381MB

From the MemoryMXBean library.
Heap Committed: 136MB
Heap Init: 64MB
Heap Used: 74MB
Heap Max: 456MB
Non Heap Committed: 73MB
Non Heap Init: 4MB
Non Heap Used: 72MB

Current idle threads: 4
Current total threads: 13
Current busy threads: 9
Current queue size: 0
Max threads: 200
Min threads: 8
Idle Timeout: 60000

  PID USERNAME NLWP PRI NICE  SIZE   RES STATE    TIME   CPU COMMAND
99802 axuser   115   59    0 2037M 1471M sleep  503:46 0.14% java

这怎么可能呢?top命令报告的使用情况要高得多。我原本以为RES应该接近堆和非堆内存之和。

然而,pmap -x报告的大部分内存都在堆中:

Address     Kbytes       RSS       Anon     Locked Mode   Mapped File
*102000         56         56         56       - rwx----    [ heap ]
*110000       3008       3008       2752       - rwx----    [ heap ]
*400000    1622016    1621056    1167568       - rwx----    [ heap ]
*000000      45056      45056      45056       - rw-----    [ anon ]

有人能否请解释一下这个问题?我完全不知道该怎么做。

谢谢。

更新

在Linux上似乎没有这个问题。

另外,根据Peter Lawrey的回答,“pmap”报告的“堆”是本地堆而不是Java堆。


5
这个应用程序中是否使用了任何原生库? - JimmyJames
6
pmap报告的“heap”可能是本地堆而不是Java堆。 应用程序在本地空间中可能使用哪些资源? - Peter Lawrey
1
需要考虑的一件事是您的数据库事务是否得到清理。您可能有一个连接池,如果您不清理它们就不断创建语句,它们可能仍然挂在本地空间中。 - JimmyJames
2
尽管名称为如此,但它只是“方法区”,例如PermGen或MetaSpace。https://docs.oracle.com/javase/8/docs/api/java/lang/management/MemoryMXBean.html 它不包括堆栈、GUI组件、共享库、直接内存或其他本机内存。 - Peter Lawrey
1
假设在调用之前变量没有被重新分配或设置为空,那应该可以工作。另外,如果您正在使用连接池,关闭操作实际上不会关闭连接,而只是将其返回到池中。但是,如果您可靠地清理语句,这不应该成为问题。 - JimmyJames
显示剩余8条评论
2个回答

2

我遇到了类似的问题并找到了解决方案:

Solaris 11
JDK10
REST application using HTTPS (jetty server)
There was a significant increase of c-heap (observed via pmap) over time

我决定使用libumem进行一些压力测试。 所以我用以下命令开始进程:

UMEM_DEBUG=default UMEM_LOGGING=transaction LD_PRELOAD=libumem.so.1

并通过https请求对应用程序进行了压力测试。 一段时间后,我使用mdb连接到了该进程。 在mdb中,我使用了命令::findleaks,并显示这个作为泄漏:

libucrypto.so.1`ucrypto_digest_init

看起来JCA(Java加密架构)的实现OracleUcrypto在Solaris上存在一些问题。

通过更新$JAVA_HOME/conf/security/java.security文件解决了这个问题 - 我将OracleUcrypto的优先级更改为3,SUN实现的优先级更改为1。

security.provider.3=OracleUcrypto
security.provider.2=SunPKCS11 ${java.home}/conf/security/sunpkcs11-solaris.cfg
security.provider.1=SUN

之后问题消失了。

这也解释了为什么在Linux上没有问题 - 因为有不同的JCA提供程序实现在运行。


很好,我很高兴你解决了它。鉴于这是很久以前的事情,我无法验证这个解决方案是否适用于我。但这绝对看起来可以解决我的问题。我会点赞以提高其可见性。 - Srki Rakic

1
在垃圾收集环境中,保留未使用的指针会导致“泄漏失败”,并阻止GC执行其任务。很容易意外地保留指针。 常见的罪魁祸首是哈希表。另一个是逻辑上已清除(通过将重用索引设置为0),但实际上数组内容(在使用索引之上)仍然指向某些东西的数组或向量。

你所描述的会不会导致堆内存泄漏? - Srki Rakic
4
原问题提到堆大小(比较)稳定;增长的是“本机”内存大小。这让我想到可能存在一个与Java对象无关的泄漏。在这里,Hashtable不是问题,否则它们会在堆转储中显示出大量活动的Java对象。 - Christopher Schultz
好的观点。泄漏的资源很可能是罪魁祸首。例如打开但未关闭的文件,启动但未终止的线程。一些类型的存储,如类,则根本无法被收集。 - ddyer

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