【问题标题】:How shall I diagnose memory leakage in this long-running java program?我该如何诊断这个长时间运行的 java 程序中的内存泄漏?
【发布时间】:2015-11-21 00:46:34
【问题描述】:

我尝试使用jmap/eclipse/jvisualvm等来诊断问题,但没有太大进展。您的任何建议都会被采纳!

我们有一个长期运行的 java 应用程序存在内存泄漏问题。我们使用以下设置来启动程序。我们使用 java 1.7.0_67。

java -server -Xmx500M -Xms500M -XX:NewSize=300M \
     -verbosegc -Xloggc:/var/log/singer/gc.log -XX:+UseGCLogFileRotation \
     -XX:NumberOfGCLogFiles=100 -XX:GCLogFileSize=2M -XX:+PrintGCDetails \
     -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintClassHistogram \
     -XX:+UseConcMarkSweepGC -XX:+UseParNewGC 

运行几天后,“top -p”会显示如下:

PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND                                                                                                                               
29503 root      35  15 8240m 1.2g  14m S   36  2.0 215:31.18 java                                                                                                                                   

'top' 命令显示我们程序的常驻内存使用量为 1.2G。这远远超过了我们设置的 500M 最大堆大小。

以下显示了一些 jvm 指标。计数在 1.2G 附近。该程序在启动时没有使用这么多内存。

jvm_gc_ConcurrentMarkSweep_cycles: 5
jvm_gc_ConcurrentMarkSweep_msec: 110
jvm_gc_ParNew_cycles: 26129
jvm_gc_ParNew_msec: 130964
jvm_gc_cycles: 26134
jvm_gc_msec: 131074    
jvm_buffer_direct_count: 27
jvm_buffer_direct_max: 463077
jvm_buffer_direct_used: 463077
jvm_buffer_mapped_count: 0
jvm_buffer_mapped_max: 0
jvm_buffer_mapped_used: 0
jvm_classes_current_loaded: 2821
jvm_classes_total_loaded: 2821
jvm_classes_total_unloaded: 0
jvm_compilation_time_msec: 12976
jvm_current_mem_CMS_Old_Gen_max: 209715200
jvm_current_mem_CMS_Old_Gen_used: 82458736
jvm_current_mem_CMS_Perm_Gen_max: 85983232
jvm_current_mem_CMS_Perm_Gen_used: 20445832
jvm_current_mem_Code_Cache_max: 50331648
jvm_current_mem_Code_Cache_used: 4465792
jvm_current_mem_Par_Eden_Space_max: 251658240
jvm_current_mem_Par_Eden_Space_used: 131968344
jvm_current_mem_Par_Survivor_Space_max: 31457280
jvm_current_mem_Par_Survivor_Space_used: 2681328
jvm_current_mem_used: 242020032
jvm_fd_count: 493
jvm_fd_limit: 65536
jvm_heap_committed: 492830720
jvm_heap_max: 492830720
jvm_heap_used: 217095032
jvm_nonheap_committed: 38780928
jvm_nonheap_max: 136314880
jvm_nonheap_used: 24911624
jvm_num_cpus: 32
jvm_post_gc_CMS_Old_Gen_max: 209715200
jvm_post_gc_CMS_Old_Gen_used: 13095808
jvm_post_gc_CMS_Perm_Gen_max: 85983232
jvm_post_gc_CMS_Perm_Gen_used: 20444448
jvm_post_gc_Par_Eden_Space_max: 251658240
jvm_post_gc_Par_Eden_Space_used: 0
jvm_post_gc_Par_Survivor_Space_max: 31457280
jvm_post_gc_Par_Survivor_Space_used: 2681328
jvm_post_gc_used: 36221584
jvm_start_time: 1440568584192
jvm_thread_count: 65
jvm_thread_daemon_count: 25
jvm_thread_peak_count: 79
jvm_uptime: 50765537

进程状态:

Name:   java
State:  S (sleeping)
Tgid:   29503
Ngid:   0
Pid:    29503
PPid:   1
TracerPid:  0
Uid:    0   0   0   0
Gid:    0   0   0   0
FDSize: 1024
Groups: 0 
VmPeak:  8440764 kB
VmSize:  8439440 kB
VmLck:         0 kB
VmPin:         0 kB
VmHWM:   1232168 kB
VmRSS:   1232168 kB
VmData:  8386608 kB
VmStk:       136 kB
VmExe:         4 kB
VmLib:     15320 kB
VmPTE:      3296 kB
VmSwap:        0 kB
Threads:    104
SigQ:   0/241457
SigPnd: 0000000000000000
ShdPnd: 0000000000000000
SigBlk: 0000000000000000
SigIgn: 0000000000000002
SigCgt: 2000000181005ccd
CapInh: 0000000000000000
CapPrm: 0000003fffffffff
CapEff: 0000003fffffffff
CapBnd: 0000003fffffffff
Seccomp:    0
Cpus_allowed:   ffffffff,ffffffff,ffffffff,ffffffff
Cpus_allowed_list:  0-127
Mems_allowed:   00000000,00000003
Mems_allowed_list:  0-1 
voluntary_ctxt_switches:    52
nonvoluntary_ctxt_switches: 2

【问题讨论】:

    标签: java performance memory memory-leaks profiling


    【解决方案1】:

    这一定是个骗子,但让我给你一个快速提示

    首先,您需要某种分析器。有很多可供选择的可以做到这一点。让探查器针对您的应用运行,然后执行以下操作:

    • 运行 2 次垃圾回收(分析器可以执行此操作)
    • 创建一个转储以保存您的课程数量
    • 让您的程序运行一段时间,时间长到足以丢失一些内存
    • 再次运行 2 次垃圾回收
    • 进行第二次转储
    • 区分两个转储(分析器中应该有一个函数可以让您增加类计数,因为第一次转储使这变得非常容易)

    您正在寻找的是一个或多个课程显着增加的课程数量。一旦你找到了这个,你所要做的就是弄清楚什么是指(持有一个引用)那个类(也应该在你的转储中的某个地方),这就是你的泄漏。当不止一个类的数量增加时,查找包含对其他类的引用的根类——这是你必须释放的类。

    这不是你的 VM 设置或类似的东西,只是一个简单的编程错误,保留了你认为已释放的引用——比如添加一个侦听器而不删除它或忘记处理帧。

    【讨论】:

    • 我已经使用 jmap 对进程进行了一些转储,并使用 eclipse 内存分析器对其进行了分析。有趣的是,eclipse 内存分析器报告的堆内存使用量比我们使用 top 观察到的要小得多。
    • Jim 占用大量内存,你不能放弃任何一次测量,你需要寻找班级计数的差异。
    • 我可能会漏掉一些东西,你能解释一下'Jim'吗?你能推荐一些我可以使用的分析器吗? Jprofiler 需要许可证,而我没有。
    • 抱歉,电话打错了——我的意思是 JVM 可以使用大量内存,这些内存会在 PS 中显示,但在 eclipse 中不会,因此内存不能很好地指示问题。 VisualVM 比 jprofiler 稍微难一些,但应该能够为您提供实例计数(并且它与 JDK 一起分发)。
    • 谢谢!我对visualvm有一些问题——程序在主机上远程运行,它不支持gui。我尝试使用 jstatd,但到目前为止还没有成功。
    【解决方案2】:

    为了找到泄漏,我们通常使用 JMeter/VisualVM 堆转储的组合。程序如下:

    • 启动测试应用并使用分析器连接到 JVM(在我们的例子中是 VisulaVM)
    • 启动 JMeter 脚本以模拟实际应用使用情况
    • 在可视 VM 中启动 Sampler,如果应用程序很大,则创建内存转储并单独分析。在转储中 - 查看为您的应用程序创建的字节数创建的类实例数。最初,请注意您的应用程序特定类。

    根据您在描述中提供的详细信息,很难说泄漏的根本原因在哪里。你应该更了解你的应用程序(它可能是没有被 GC 清理的会话 bean 等),但正如我提到的,内存转储是一个很好的开始。

    对于真正的生产应用程序服务器,配置 JMX 以便稍后解决此类问题总是好的。

    articles to start with 之一。

    【讨论】:

      猜你喜欢
      • 2013-07-11
      • 2018-10-24
      • 2012-10-09
      • 2010-09-30
      • 1970-01-01
      • 2015-06-08
      • 1970-01-01
      • 1970-01-01
      • 2015-11-07
      相关资源
      最近更新 更多