本机 C 和 Fortran 代码的 Java 内存泄漏

Posted

技术标签:

【中文标题】本机 C 和 Fortran 代码的 Java 内存泄漏【英文标题】:Java memory leak with native C and Fortran code 【发布时间】:2014-04-14 12:10:18 【问题描述】:

我正在开发一个旧的 java 程序,其中包含一个带有 Fortran 调用的本机库。

所以,我有 Java 通过 JNI 调用 C,然后调用 Fortran。

在生产中,我们有一个内存不足错误,例如:

Native memory allocation (malloc) failed to allocate 120000 bytes for jfloat in C:\BUILD_AREA\jdk6_37\hotspot\src\share\vm\prims\jni.cpp

我怀疑是内存泄漏。

我是公司的新人,我想在 linux 上工作,但他们让我在 Windows 上工作:( 在生产中,我们使用 .so 文件,因为我们在 solaris 上,而我在 Windows 上使用 DLL(逻辑上)。

首先,我尝试重现生产问题。所以,我创建了一个单元测试来加载DLL并多次调用调用native方法的java类。 当我这样做时,我使用 processExplorer.exe 看到内存每 2 秒增长到 2MB。我有一个例外,就像在生产中一样。

我很高兴我成功地重现了这个问题,我可以说这个问题来自 C 或 Fortran 代码。

接下来,我尝试删除对 Fortran 的调用,而我的 java 只调用了 C(没有 Fortran,这个测试让我可以查看问题是来自 C 还是 Fortran。)

结果就是内存没动!凉爽的!我可以说我对 C 语言中的 malloc/free 没有任何问题。

所以,我决定学习一点 Fortran 来查看代码。 :)

我了解到,在 Fortran 中,我们可以使用 allocate 和 deallocate 关键字来处理内存。而且我的代码不包含这些关键字。 :(

在这一切之后,有人允许我在 Solaris 上启动我的 junit 测试,该测试调用 Java->JNI->C=>Fortran 并使用 .so 而不是 DLL。

还有惊喜 - 内存没有移动!!!我在 Solaris 或 RedHat 下没有任何问题。

我被卡住了,因为问题存在于生产中,但我无法清楚地重现它。 :(

为什么我发现 DLL 和 SO 之间存在差异?代码(java/C/Fortran)完全一样,因为是我编译的。

如何进行更多调查?

我尝试在重现问题的 Windows 下进行内存转储,但我什么也没看到。

是jvm的问题吗?还是问题出在通过 JNI 传递给 C 的对象上?

非常感谢您帮助我解决这个问题。

信息:我使用的是 Windows 7 64 位

PS:我是法国人,请原谅我的英语。我每次都尽力做到最好。 ;)

这是 C 代码的标题:

    #ifndef unix 
       __MINGW_IMPORT void modlin_OM(float pmt[], float abaque[][], float don[][], float cond[], float res[][], int flag[]) ; 
    #else 
       extern void modlin_om_(float * pmt, float * abaque, float * don, float * cond, float * res, int * flag) ; 
    #endif

方法之后:

   JNIEXPORT jint JNICALL Java_TrtModlin_modlin_1OM
     (JNIEnv * env, jobject obj, 
 jfloatArray pmtPar, 
 jobjectArray abaquePar, jobjectArray donPar, jfloatArray condPar, jobjectArray resPar,  jintArray flagPar)
   

一些代码,以及 Fortran 的方法调用

   #ifndef unix
      modlin_OM(pmt, abaque, don, cond, res, & iFlag) ;
   #else
modlin_om_(pmt, abaque, don, cond, res, & iFlag) ;
   #endif

正如我之前所说,我通过删除这些行来测试对 C 的调用并且内存没有增长 :( 我通过使用 free(someVar) 删除一行进行测试并且内存增长,因为在这种情况下没有完成 free . 这就是为什么我得出结论认为我的 C 可以使用 Free/Malloc。

【问题讨论】:

你有两个平台(Unix 和 Windows)的 C 绑定的源代码吗? 是的,我有源代码,但对于 windows 或 unix 是一样的,我在我的问题末尾添加它(如果你想要所有源代码,我可以给你) 哪个fortran编译器? 我们使用:FORTRANCOMPILER=f90 不多说了,哪个Fortran厂商?在 Solaris 上 f90 将是 Sun/Oracle,但在 Windows 上它可以是任何东西。 【参考方案1】:

分析内存问题总是很复杂的。根据我的经验,有两种方法:

1) 你尝试复制。这假设您有源代码和根本原因的想法。 2)您观察生产崩溃:频率、与其他事件的相关性等。

这可以帮助确定它是否是内存泄漏(在业务负载下可能是高消耗......)

在您的特定情况下,我注意到以下几点:

    代码的行为在不同的操作系统上可能不同。 Java 代码(JVM 错误)非常罕见。本机代码经常出现这种情况(例如,忘记关闭 ZIP 会导致 Linux 上的内存泄漏,但在 Windows 上不会...)

    在 C 标头 (*.h) 中:abaque、don 和 res 在 Windows 上是“float * *”,在 Unix 上是“float *”。这可能是您的 C 标头中的错误,或者这意味着 C 实现不期望根据操作系统使用相同的参数类型(这对我来说很奇怪......)

在第二种情况下,您在 Windows(不是目标)上编译 C 头文件的事实可以解释您没有生成正确的 JNI 存根(典型的交叉编译问题)...从这里我们可以做很多假设,简单或非常复杂......

祝你好运!

【讨论】:

【参考方案2】:

我在我的应用程序中遇到了同样的问题,但我提出了很好的解决方案。 请在使用 make null 之后创建 native 的对象,以便 gc 收集未引用的对象。

【讨论】:

欢迎,请尽量在每个句子的开头使用大写字母。

以上是关于本机 C 和 Fortran 代码的 Java 内存泄漏的主要内容,如果未能解决你的问题,请参考以下文章

使用 GCC 对模块内的 fortran 子例程进行外部命名

使用 GCC 对模块内的 fortran 子例程进行外部命名

如何编译C/Fortran动态/静态链接库

Tidekit 和 C#/Java/C++ 本机代码兼容性

有没有一种方法可以在100到1000个索引的范围内打印长数组到GDB中的txt文件? (调试Fortran代码)

Fortran与C的混编