3年前 (2020-12-05)  jvm |   抢沙发  437 
文章评分 0 次,平均分 0.0

诊断Java语言代码中的内存泄漏

使用NetBeans profiler诊断Java语言代码中的泄漏。

诊断Java语言代码中的漏洞可能很困难。通常,它需要非常详细的应用知识。此外,这个过程通常是反复的和冗长的。本节提供有关可用于诊断Java语言代码中内存泄漏的工具的信息。

除了本节提到的工具之外,还有大量第三方内存调试器工具。Eclipse内存分析器工具(MAT)和你的工具包(www.yourkit.com)是两个具有内存调试功能的商业工具示例。还有很多其他产品,不推荐具体的产品。

以下实用程序用于诊断Java语言代码中的泄漏。

NetBeans Profiler:NetBeans Profiler可以非常快速地找到内存泄漏。商业内存泄漏调试工具在大型应用程序中查找泄漏可能需要很长时间。然而,NetBeans Profiler使用这种对象通常演示的内存分配和重新聚集模式。这一过程还包括缺乏记忆的重新聚集。探查器可以检查这些对象的分配位置,这通常足以确定泄漏的根本原因。

请参阅NetBeans Profiler

以下各节介绍诊断Java语言代码中泄漏的其他方法。

获取 Heap Histogram 堆直方图

可以使用不同的命令和选项来获取堆直方图来识别内存泄漏。

您可以通过检查堆直方图来快速缩小内存泄漏的范围。您可以通过以下几种方式获取堆直方图:

如果Java进程是用-XX:+printclashshistogram命令行选项启动的,那么Control+Break处理程序将生成一个堆直方图。

可以使用jmap实用程序从正在运行的进程获取堆直方图:

建议使用最新的实用程序jcmd,而不是jmap实用程序,以增强诊断并降低性能开销。查看jcmd的有用命令实用工具下面示例中的命令使用jcmd为正在运行的进程创建堆直方图,结果与下面的jmap命令类似。

jcmd <process id/main class> GC.class_histogram filename=Myheaphistogram
jmap -histo pid

输出显示堆中每个类类型的总大小和实例计数。如果获得了一系列柱状图(例如,每2分钟一次),那么您可能会看到一个趋势,从而导致进一步的分析。

可以使用jmap实用程序从核心文件获取堆直方图,如下例所示

jmap -histo core_file

例如,如果在运行应用程序时指定-XX:+HeapDumpOnOutOfMemoryError命令行选项,那么当抛出OutOfMemoryError异常时,JVM将生成一个堆转储。然后可以在下面的示例中执行jmap

$ jmap -histo \ /java/re/javase/6/latest/binaries/solaris-sparc/bin/java core.27421

Attaching to core core.27421 from executable 
/java/re/javase/6/latest/binaries/solaris-sparc/bin/java, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 1.6.0-beta-b63
Iterating over heap. This may take a while...
Heap traversal took 8.902 seconds.

Object Histogram:
 
Size      Count   Class description
-------------------------------------------------------
86683872  3611828 java.lang.String
20979136  204     java.lang.Object[]
403728    4225    * ConstMethodKlass
306608    4225    * MethodKlass
220032    6094    * SymbolKlass
152960    294     * ConstantPoolKlass
108512    277     * ConstantPoolCacheKlass
104928    294     * InstanceKlassKlass
68024     362     byte[]
65600     559     char[]
31592     359     java.lang.Class
27176     462     java.lang.Object[]
25384     423     short[]
17192     307     int[]
:

上面的示例显示OutOfMemoryError异常是由java.lang.String对象(堆中有3611828个实例)。如果没有进一步的分析,就不清楚字符串被分配到何处。然而,这些信息仍然有用。

监视挂起终结的对象

可用于监视挂起命令的不同对象和终结选项。

OutOfMemoryError异常与“Java堆空间”详细信息一起引发时,原因可能是过度使用终结器。要诊断此问题,您可以使用多个选项来监视挂起终结的对象数:

  • JConsole管理工具可用于监视挂起终结的对象数。此工具在“摘要”选项卡窗格的内存统计信息中报告挂起的终结计数。这个计数是近似的,但它可以用来描述应用程序的特征,并了解它是否很大程度上依赖于定案。
  • 在oracle solaris和Linux操作系统上,jmap实用程序可以与-finalizerinfo选项一起使用,以打印有关等待完成的对象的信息。
  • 应用程序可以使用java.lang.management.MemoryMXBean类。API文档和示例代码的链接可以在自定义诊断工具中找到。示例代码可以很容易地进行扩展,以包括挂起的定案计数的报告。

诊断本机代码中的泄漏

可以使用多种技术来查找和隔离本机代码内存泄漏。一般来说,没有一个理想的解决方案适用于所有平台。

以下是一些诊断本机代码泄漏的技术。

跟踪所有内存分配和空闲调用

可用于跟踪所有内存分配和内存使用情况的工具。

所有本地调用的自由分配都是非常常见的。这可以是一个相当简单的过程,也可以是一个非常复杂的过程。多年来,许多产品都是围绕本机堆分配和内存使用的跟踪而构建的。

像IBM rational purify和sun studiodbx调试器的运行时检查功能可以用来在正常的本机代码情况下查找这些泄漏,也可以找到对本机堆内存的任何访问,这些内存表示对未初始化内存的分配或对释放内存的访问。请参阅使用dbx调试器查找泄漏。

并不是所有这些类型的工具都能与使用本机代码的Java应用程序一起工作,而且这些工具通常都是特定于平台的。因为虚拟机在运行时动态地创建代码,所以这些工具可能会错误地解释代码并根本无法运行,或者提供错误的信息。请与您的工具供应商联系,以确保该工具的版本与您正在使用的虚拟机的版本兼容。

请参阅sourceforge以获取许多简单和可移植的本机内存泄漏检测示例。大多数库和工具都假定您可以重新编译或编辑应用程序的源代码,并将包装器函数置于分配函数之上。这些工具中功能更强大的工具允许您通过动态插入这些分配函数来运行应用程序。图书馆就是这样libumem.所以首先在Oracle Solaris 9操作系统更新3中引入;请参阅使用libumem工具查找泄漏。

跟踪库中的所有内存分配

如果您编写了一个JNI库,那么考虑使用一个简单的包装器方法来创建一个本地化的方法来确保您的库不会泄漏内存。

下例中的过程是一种简单的JNI库本地化分配跟踪方法。首先,在所有源文件中定义以下行。

#include <stdlib.h>
#define malloc(n) debug_malloc(n, __FILE__, __LINE__)
#define free(p) debug_free(p, __FILE__, __LINE__)

然后,您可以使用以下示例中的函数来监视泄漏。

/* Total bytes allocated */
static int total_allocated;
/* Memory alignment is important */
typedef union { double d; struct {size_t n; char *file; int line;} s; } Site;
void *
debug_malloc(size_t n, char *file, int line) 
{ 
    char *rp;
    rp = (char*)malloc(sizeof(Site)+n); 
    total_allocated += n; 
    ((Site*)rp)->s.n = n;
    ((Site*)rp)->s.file = file;
    ((Site*)rp)->s.line = line;
    return (void*)(rp + sizeof(Site));
}
void 
debug_free(void *p, char *file, int line)
{
    char *rp;
    rp = ((char*)p) - sizeof(Site);
    total_allocated -= ((Site*)rp)->s.n;
    free(rp);
}

然后JNI库需要定期(或在关闭时)检查total_allocated变量的值,以验证它是否有意义。前面的代码也可以展开,将剩余的分配保存在一个链接列表中,并报告泄漏内存的分配位置。这是一种本地化的、可移植的方法,用于跟踪单个源集中的内存分配。您需要确保只使用来自debug_malloc()的指针调用debug_free(),如果使用了realloc()calloc()strdup()等函数,还需要为它们创建类似的函数。

查找本机堆内存泄漏的一种更全局的方法是在整个进程中插入库调用。

使用操作系统支持跟踪内存分配

可用于跟踪操作系统内存分配的工具。

  • 大多数操作系统都包含某种形式的全局分配跟踪支持。
  • 在Windows上,搜索MSDN库以获得调试支持。微软C++编译器具有/Md和/MDD编译器选项,这些选项将自动包含跟踪内存分配的额外支持。
  • Linux系统有mtrace和libnjamd等工具来帮助处理分配跟踪。
  • oracle solaris操作系统提供了watchmalloc工具。oracles solaris9操作系统更新3也引入了libumem工具。请参见使用libumem工具查找泄漏。

使用dbx调试器查找泄漏

dbx调试器包含运行时检查(RTC)功能,可以查找泄漏。dbx调试器是oracle solari studio的一部分,也可用于Linux。

下面的示例显示了一个示例dbx会话。

$ dbx ${java_home}/bin/java
Reading java
Reading ld.so.1
Reading libthread.so.1
Reading libdl.so.1
Reading libc.so.1
(dbx) dbxenv rtc_inherit on
(dbx) check -leaks
leaks checking - ON
(dbx) run HelloWorld
Running: java HelloWorld 
(process id 15426)
Reading rtcapihook.so
Reading rtcaudit.so
Reading libmapmalloc.so.1
Reading libgen.so.1
Reading libm.so.2
Reading rtcboot.so
Reading librtc.so
RTC: Enabling Error Checking...
RTC: Running program...
dbx: process 15426 about to exec("/net/bonsai.sfbay/export/home2/user/ws/j2se/build/solaris-i586/bin/java")
dbx: program "/net/bonsai.sfbay/export/home2/user/ws/j2se/build/solaris-i586/bin/java"
just exec'ed
dbx: to go back to the original program use "debug $oprog"
RTC: Enabling Error Checking...
RTC: Running program...
t@1 (l@1) stopped in main at 0x0805136d
0x0805136d: main       :        pushl    %ebp
(dbx) when dlopen libjvm { suppress all in libjvm.so; }
(2) when dlopen libjvm { suppress all in libjvm.so; }  
(dbx) when dlopen libjava { suppress all in libjava.so; }
(3) when dlopen libjava { suppress all in libjava.so; }  
(dbx) cont                                             
Reading libjvm.so
Reading libsocket.so.1
Reading libsched.so.1
Reading libCrun.so.1
Reading libm.so.1
Reading libnsl.so.1
Reading libmd5.so.1
Reading libmp.so.2
Reading libhpi.so
Reading libverify.so
Reading libjava.so
Reading libzip.so
Reading en_US.ISO8859-1.so.3
hello world
hello world
Checking for memory leaks...

Actual leaks report    (actual leaks:           27  total size:      46851 bytes)

  Total     Num of  Leaked     Allocation call stack
  Size      Blocks  Block
                    Address
==========  ====== =========== =======================================
     44376       4      -      calloc < zcalloc 
      1072       1  0x8151c70  _nss_XbyY_buf_alloc < get_pwbuf < _getpwuid <
                               GetJavaProperties < Java_java_lang_System_initProperties <
                               0xa740a89a< 0xa7402a14< 0xa74001fc
       814       1  0x8072518  MemAlloc < CreateExecutionEnvironment < main 
       280      10      -      operator new < Thread::Thread 
       102       1  0x8072498  _strdup < CreateExecutionEnvironment < main 
        56       1  0x81697f0  calloc < Java_java_util_zip_Inflater_init < 0xa740a89a<
                               0xa7402a6a< 0xa7402aeb< 0xa7402a14< 0xa7402a14< 0xa7402a14
        41       1  0x8072bd8  main 
        30       1  0x8072c58  SetJavaCommandLineProp < main 
        16       1  0x806f180  _setlocale < GetJavaProperties <
                               Java_java_lang_System_initProperties < 0xa740a89a< 0xa7402a14<
                               0xa74001fc< JavaCalls::call_helper < os::os_exception_wrapper 
        12       1  0x806f2e8  operator new < instanceKlass::add_dependent_nmethod <
                               nmethod::new_nmethod < ciEnv::register_method <
                               Compile::Compile #Nvariant 1 < C2Compiler::compile_method <
                               CompileBroker::invoke_compiler_on_method <
                               CompileBroker::compiler_thread_loop 
        12       1  0x806ee60  CheckJvmType < CreateExecutionEnvironment < main 
        12       1  0x806ede8  MemAlloc < CreateExecutionEnvironment < main 
        12       1  0x806edc0  main 
         8       1  0x8071cb8  _strdup < ReadKnownVMs < CreateExecutionEnvironment < main 
         8       1  0x8071cf8  _strdup < ReadKnownVMs < CreateExecutionEnvironment < main 

输出显示,如果在进程即将退出时没有释放内存,dbx调试器将报告内存泄漏。但是,在初始化时分配的内存以及进程生命周期所需的内存通常不会在本机代码中释放。因此,在这种情况下,dbx调试器可以报告实际上不是泄漏的内存泄漏。

注意:

上一个示例使用了两个抑制命令来抑制虚拟机中报告的泄漏:libjvm.so文件以及Java支持库,libjava.so文件.

使用libumem工具查找漏洞

在Oracle Solaris 9操作系统更新3中首次引入libumem.所以库和模块化调试器mdb可以用来调试内存泄漏。

在使用libumem之前,必须预加载libumem库并设置一个环境变量,如下例所示。

$ LD_PRELOAD=libumem.so
$ export LD_PRELOAD
$ UMEM_DEBUG=default
$ export UMEM_DEBUG

现在,运行Java应用程序,但在它退出之前停止它。下面的示例使用truss在进程调用_exit系统调用时停止该进程。

$ truss -f -T _exit java MainClass arguments

此时,您可以附加mdb调试器,如下例所示。

$ mdb -p pid
>::findleaks

findleaks 命令是用于查找内存泄漏的mdb命令。如果发现泄漏,则此命令将打印分配调用的地址、缓冲区地址和最近的符号。

还可以通过转储bufctl结构获取导致内存泄漏的分配的堆栈跟踪。此结构的地址可以从::findleaks命令的输出中获取。

 

除特别注明外,本站所有文章均为老K的Java博客原创,转载请注明出处来自https://javakk.com/950.html

关于

发表评论

表情 格式

暂无评论

登录

忘记密码 ?

切换登录

注册