gdb调试core分析jvm(JNI)奔溃原因

本文分析了一个使用JNI调用C++ so库的Java服务在并发测试中突然崩溃的问题。通过gdb分析core dump文件,定位到C++源码中的异常抛出点,并最终通过调整异常处理方式解决了问题。

  前几天服务(服务中使用了JNI调用了C++的so库)在并发测试几天后jvm突然奔溃,只在控制台打印出了一句话: terminate called after throwing an instance of '._0'。因为只根据这句话无法确定奔溃原因,于是查看linux系统日志,进入/var/log下,打开message文件,看到下面的话:

Sep 16 13:44:04 localhost abrt[37667]: Saved core dump of pid 4865 (/home/dear/smb/environments/jdk/bin/java) to /var/spool/abrt/ccpp-2017-09-16-13:27:35-4865 (6602772480 bytes)
Sep 16 13:44:04 localhost abrtd: Directory 'ccpp-2017-09-16-13:27:35-4865' creation detected
Sep 16 13:44:07 localhost abrtd: Executable '/home/dear/smb/environments/jdk/bin/java' doesn't belong to any package and ProcessUnpackaged is set to 'no'
Sep 16 13:44:07 localhost abrtd: 'post-create' on '/var/spool/abrt/ccpp-2017-09-16-13:27:35-4865' exited with 1
Sep 16 13:44:07 localhost abrtd: Deleting problem directory '/var/spool/abrt/ccpp-2017-09-16-13:27:35-4865'
是因为系统设置的问题,没有生成core dump文件,执行ulimit -c为0,于是修改系统配置,使其为unlimited,再次启动服务,期待问题浮现。果然在运行了几天后,又出现了 同样的问题,且生成了core dump文件。使用gdb对core文件进行分析,执行命令:gdb /home/dear/smb/environments/jdk/bin/java(java命令路径) core.4865(core dump文件名称)。然后键入bt命令,出现下列信息:

#0  0x0000003fe60323ae in raise (sig=6) at ../nptl/sysdeps/unix/sysv/linux/raise.c:67
#1  0x0000003fe60337f5 in abort () at abort.c:93
#2  0x00007f06386efcbd in __gnu_cxx::__verbose_terminate_handler() () at ../../../../gcc-4.9.2/libstdc++-v3/libsupc++/vterminate.cc:95
#3  0x00007f06386b2956 in __cxxabiv1::__terminate(void (*)()) () at ../../../../gcc-4.9.2/libstdc++-v3/libsupc++/eh_terminate.cc:47
#4  0x00007f06386b29a1 in std::terminate() () at ../../../../gcc-4.9.2/libstdc++-v3/libsupc++/eh_terminate.cc:57
#5  0x00007f06386b27c8 in __cxa_throw () at ../../../../gcc-4.9.2/libstdc++-v3/libsupc++/eh_throw.cc:87
#6  0x00007f06386a8f29 in ApLicenser::CutEffInfo (this=Unhandled dwarf expression opcode 0xf3
) at ap_license.cpp:64
#7  0x00007f06386a907e in ApLicenser::<lambda()>::operator()(void) const (__closure=0x7f0630290210) at ap_license.cpp:171
#8  0x00007f06386aa652 in ApLicenser::CatchInfo (this=Unhandled dwarf expression opcode 0xf3
) at ap_license.cpp:182
#9  0x00007f06386ab46e in ApLicenser::CheckLicense (this=0x7f0640a28700, mark=Unhandled dwarf expression opcode 0xf3
) at ap_license.cpp:269
#10 0x00007f06386abe0a in ApLicenser::CopyrightCheck (this=Unhandled dwarf expression opcode 0xf3
) at ap_license.cpp:414
#11 0x00007f06386a07b6 in ApHandler::CopyrightCheck (this=0x7f06407befd0, request_ip=..., user_num=-1, model_num=-1, verify_num=-1) at ../ap/ap_server.cpp:28
#12 0x00007f06386a2bdd in Java_com_dear_smb_bus_jni_NativeJNIInterface_checkLocalIpInfo (env=0x7f062ae279e8, clazz=Unhandled dwarf expression opcode 0xf3
) at vpr_model.cpp:387
#13 0x00007f0649a89d98 in ?? ()
#14 0xffffffffffffffff in ?? ()
#15 0xffffffffffffffff in ?? ()
#16 0x00000000afd36b70 in ?? ()
#17 0x00000000afd36b98 in ?? ()
#18 0x0000000000000000 in ?? ()
上面的信息列出了出错的native堆栈,因为gdb无法分析到java的层面,所以下面会打出??。通过上面的信息,可以看到,出错的C++源码部分,即,in ApLicenser::CutEffInfo (this=Unhandled dwarf expression opcode 0xf3) at ap_license.cpp:64,查看ap_license.cpp的64行,如下

void ApLicenser::CutEffInfo(const std::string str_order, std::string &result) {
    FILE* fp;  
    char   buf[1024];        
    bzero(buf, sizeof(buf)); 
    fp = popen( str_order.c_str(), "r" ); 
    if (fp == NULL) {
        throw AP_UNKNOWN_ERROR;
    }
    fread( buf, sizeof(char), sizeof(buf),  fp);   //64行
    pclose( fp ); 
    result = buf;
}
通过分析发现,此方法被调用时,上层使用catch (int err)进行捕捉,而AP_UNKNOWN_ERROR为枚举类型,改为throw (int)AP_READ_LICENSE_FAILED;(进行强制转换),问题解决。


### 3.1 使用/proc文件系统查看JVM进程的内存映射 在Linux系统中,可以通过访问`/proc/<pid>/maps`文件来查看JVM进程的内存映射信息。该文件展示了进程的虚拟内存布局,包括堆、栈、JNI内存等区域的地址范围和使用情况。通过分析这些信息,可以间接了解JVM的内存使用情况,尤其是JNI Memory的使用情况,这通常与Native方法调用有关[^3]。 ```bash cat /proc/<jvm_pid>/maps ``` 其中`<jvm_pid>`是JVM进程的ID。输出内容中,每行表示一个内存段,包含起始地址、结束地址、权限、偏移量、设备号、文件名等信息。例如: ``` 7f1a2e400000-7f1a2e800000 rw-p 00000000 00:00 0 ``` 此行表示一段从`7f1a2e400000`到`7f1a2e800000`的可读写内存区域,未关联到任何文件(设备号为00:00,文件名为0),可能是堆或栈的一部分。 ### 3.2 使用pmap工具分析JVM内存分布 `pmap`是一个用于显示进程内存映射的命令行工具,可以结合`/proc/<pid>/maps`文件的信息,提供更直观的内存使用视图。执行以下命令可以查看JVM进程的内存使用情况: ```bash pmap -x <jvm_pid> ``` 输出内容包括每个内存段的地址、大小、RSS(Resident Set Size,实际使用的物理内存)、Swap使用情况等。通过分析这些数据,可以识别出占用内存较大的内存段,并进一步分析其内容。 例如: ``` Address Kbytes RSS Swap Mode Mapping 0000000000400000 4 4 0 r-x-- java 0000000000600000 4 4 0 r---- java 0000000000601000 4 4 0 rw--- java ... ``` ### 3.3 使用gperftools分析Native内存泄漏 `gperftools`是一个性能分析工具集,其中的`heap profiler`和`heap checker`可以用于检测C/C++代码中的内存泄漏问题,适用于JVM中由JNI调用导致的Native内存泄漏。安装并启用`gperftools`后,可以通过以下方式启动JVM: ```bash LD_PRELOAD=/usr/lib/libtcmalloc.so.4 export HEAPPROFILE=/tmp/java_heap_profile java -agentpath:/usr/lib/libheapchecker.so.0.0.0 -jar your_app.jar ``` 运行一段时间后,`gperftools`会生成堆转储文件,使用`pprof`工具分析这些文件可以定位未释放的内存分配点,进而找到对应的C/C++函数和Java方法[^3]。 ### 3.4 使用gdb调试JVM内存 `gdb`(GNU Debugger)是Linux系统下的调试工具,可以用于分析JVM进程的内存状态。通过`gdb`附加到JVM进程后,可以查看内存内容、堆栈信息、线程状态等。例如,使用以下命令附加到JVM进程: ```bash gdb -p <jvm_pid> ``` 进入`gdb`交互界面后,可以使用`info proc mappings`命令查看内存映射,或者使用`x`命令查看特定地址范围的内存内容: ```gdb (gdb) info proc mappings (gdb) x/100x 0x7f1a2e400000 ``` 上述命令将显示从`0x7f1a2e400000`开始的100个内存单元的内容,有助于分析内存中存储的数据类型和结构。 ### 3.5 使用crash工具分析JVM核心转储 `crash`是一个用于分析Linux内核转储文件的工具,也可以用于分析JVM的核心转储(core dump)。当JVM因内存问题崩时,系统会生成core dump文件,使用`crash`可以深入分析时的内存状态。首先需要配置系统生成core dump文件: ```bash ulimit -c unlimited echo "/tmp/core.%e.%p" > /proc/sys/kernel/core_pattern ``` 然后使用`crash`加载core dump文件和对应的内核镜像进行分析: ```bash crash /path/to/vmlinux /tmp/core.java.<pid> ``` 进入`crash`交互界面后,可以使用`ps`查看进程状态,使用`vm`查看虚拟内存信息,或者使用`mem`查看特定地址的内存内容,从而定位内存问题的根源。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值