Ric*_*sen 29 java enterprise methodology memory-leaks legacy-code
我必须在Java应用程序中发现内存泄漏.我对此有一些经验,但希望就此采用方法/策略方面的建议.欢迎任何参考和建议.
关于我们的情况:
问题: 帮助您成功捕获企业级应用程序中的泄漏的方法是什么?
Wil*_*ung 59
如果不了解底层代码,几乎是不可能的.如果您了解底层代码,那么您可以更好地从堆中获取您在堆转储中获得的大量信息.
而且,如果不知道为什么班级在那里,你就不知道是否有泄漏.
我刚刚花了几个星期来完成这个,我使用了一个迭代过程.
首先,我发现堆分析器基本没用.他们无法有效地分析巨大的堆.
相反,我几乎完全依赖于jmap直方图.
我想你是熟悉这些,但对于那些不是:
jmap -histo:live <pid> > dump.out
Run Code Online (Sandbox Code Playgroud)
创建活动堆的直方图.简而言之,它告诉您类名,以及每个类在堆中的实例数.
我每隔5分钟,每天24小时定期倾倒垃圾堆.这可能对你来说太过细化,但要点是一样的.
我对这些数据进行了几次不同的分析.
我写了一个脚本来获取两个直方图,并将它们之间的区别转出.所以,如果java.lang.String在第一个转储中是10,在第二个转储中是15,我的脚本会吐出"5 java.lang.String",告诉我它上升了5.如果它已经下降,数字将是负数.
然后我将采取其中的几个差异,删除从运行到运行的所有类,并获取结果的并集.最后,我有一个在特定时间跨度内不断增长的类列表.显然,这些是泄漏课程的主要候选人.
但是,有些类保留了一些,而其他类是GC'd.这些类总体上很容易上下,但仍然会泄漏.因此,他们可能会脱离"不断上升"的类别.
为了找到这些,我将数据转换为时间序列并将其加载到数据库Postgres中.Postgres很方便,因为它提供了统计聚合函数,因此您可以对数据进行简单的线性回归分析,并找到趋势向上的类,即使它们并不总是位于图表之上.我使用了regr_slope函数,寻找具有正斜率的类.
我发现这个过程非常成功,而且效率很高.直方图文件并不是非常大,并且很容易从主机下载它们.在生产系统上运行它们并不是非常昂贵(它们会强制执行大型GC,并且可能会阻塞VM一段时间).我在一个带有2G Java堆的系统上运行它.
现在,所有这一切都可以识别潜在泄漏的类.
这是了解如何使用类,以及它们应该或不应该是它们的用法.
例如,您可能会发现有很多Map.Entry类或其他系统类.
除非你只是简单地缓存String,否则事实上这些系统类可能是"违法者",而不是"问题".如果您正在缓存某些应用程序类,那么该类可以更好地指示您的问题所在.如果你不缓存com.app.yourbean,那么你就不会将相关的Map.Entry绑定到它.
一旦有了一些类,就可以开始抓取代码库来查找实例和引用.既然你有自己的ORM层(无论好坏),你至少可以随时查看它的源代码.如果您正在缓存ORM,它可能会缓存包装您的应用程序类的ORM类.
最后,您可以做的另一件事是,一旦您了解了类,就可以启动服务器的本地实例,使用更小的堆和更小的数据集,并使用其中一个分析器.
在这种情况下,您可以进行单元测试,该测试仅影响您认为可能泄漏的1个(或少数个)事物.例如,您可以启动服务器,运行直方图,执行单个操作,然后再次运行直方图.你泄漏的课程应该增加1(或者你的工作单位是什么).
分析器可能能够帮助您跟踪"现在泄露"类的所有者.
但是,最后,你将需要对你的代码库有一些了解,以便更好地理解什么是泄漏,什么不是,以及为什么对象存在于堆中,更不用说为什么它可以被保留作为你的堆中的泄漏.
mat*_*t b 13
看看Eclipse Memory Analyzer.它是一个很棒的工具(自包含,不需要安装Eclipse本身),1)可以非常快速地打开非常大的堆,2)有一些非常好的自动检测工具.后者并不完美,但EMA提供了许多非常好的方法来浏览和查询转储中的对象以找到任何可能的泄漏.
我过去曾用它来帮助追捕可疑的泄漏.
这个答案扩展到了@ Will-Hartung的.我申请了同样的过程来诊断我的一个内存泄漏,并认为共享细节可以节省其他人的时间.
我们的想法是让postgres'plot'时间与每个类的内存使用情况相对应,画一条线来总结增长并识别增长最快的对象:
^
|
s | Legend:
i | * - data point
z | -- - trend
e |
( |
b | *
y | --
t | --
e | * -- *
s | --
) | *-- *
| -- *
| -- *
--------------------------------------->
time
Run Code Online (Sandbox Code Playgroud)
将堆转储(需要多个)转换为一种格式,这样便于堆转储格式的postgres使用:
num #instances #bytes class name
----------------------------------------------
1: 4632416 392305928 [C
2: 6509258 208296256 java.util.HashMap$Node
3: 4615599 110774376 java.lang.String
5: 16856 68812488 [B
6: 278914 67329632 [Ljava.util.HashMap$Node;
7: 1297968 62302464
...
Run Code Online (Sandbox Code Playgroud)
到具有每个堆转储的日期时间的csv文件:
2016.09.20 17:33:40,[C,4632416,392305928
2016.09.20 17:33:40,java.util.HashMap$Node,6509258,208296256
2016.09.20 17:33:40,java.lang.String,4615599,110774376
2016.09.20 17:33:40,[B,16856,68812488
...
Run Code Online (Sandbox Code Playgroud)
使用此脚本:
# Example invocation: convert.heap.hist.to.csv.pl -f heap.2016.09.20.17.33.40.txt -dt "2016.09.20 17:33:40" >> heap.csv
my $file;
my $dt;
GetOptions (
"f=s" => \$file,
"dt=s" => \$dt
) or usage("Error in command line arguments");
open my $fh, '<', $file or die $!;
my $last=0;
my $lastRotation=0;
while(not eof($fh)) {
my $line = <$fh>;
$line =~ s/\R//g; #remove newlines
# 1: 4442084 369475664 [C
my ($instances,$size,$class) = ($line =~ /^\s*\d+:\s+(\d+)\s+(\d+)\s+([\$\[\w\.]+)\s*$/) ;
if($instances) {
print "$dt,$class,$instances,$size\n";
}
}
close($fh);
Run Code Online (Sandbox Code Playgroud)
创建一个表来放入数据
CREATE TABLE heap_histogram (
histwhen timestamp without time zone NOT NULL,
class character varying NOT NULL,
instances integer NOT NULL,
bytes integer NOT NULL
);
Run Code Online (Sandbox Code Playgroud)
将数据复制到新表中
\COPY heap_histogram FROM 'heap.csv' WITH DELIMITER ',' CSV ;
Run Code Online (Sandbox Code Playgroud)
针对size(num of bytes)查询运行slop查询:
SELECT class, REGR_SLOPE(bytes,extract(epoch from histwhen)) as slope
FROM public.heap_histogram
GROUP BY class
HAVING REGR_SLOPE(bytes,extract(epoch from histwhen)) > 0
ORDER BY slope DESC
;
Run Code Online (Sandbox Code Playgroud)
解释结果:
class | slope
---------------------------+----------------------
java.util.ArrayList | 71.7993806279174
java.util.HashMap | 49.0324576155785
java.lang.String | 31.7770770326123
joe.schmoe.BusinessObject | 23.2036817108056
java.lang.ThreadLocal | 20.9013528767851
Run Code Online (Sandbox Code Playgroud)
斜率是每秒添加的字节数(因为纪元的单位是以秒为单位).如果使用实例而不是大小,那么这就是每秒添加的实例数.
我创建这个joe.schmoe.BusinessObject的代码行之一负责内存泄漏.它正在创建对象,将其附加到数组而不检查它是否已存在.其他对象也与泄漏代码附近的BusinessObject一起创建.