在 Java 应用程序中使用的 OpenCV JNI 库中的内存使用跟踪工具

Nul*_*oet 7 java java-native-interface opencv memory-leaks memory-management

有哪些工具可用于跟踪 Java 应用程序中 Opencv JNI 库分配的内存。

在我的 java dropwizard 服务器中,我使用 JNI opencv 绑定。当服务器打开时,java 堆内存似乎没有增加超过 GB,它会定期被 GC 释放。但是巨大的(4-5 GB)内存被添加到java进程中,不确定它来自哪里。

如何跟踪 JNI 库中分配的内存并识别是否有泄漏。

Nic*_*tto 5

@concision 的响应是正确的,对于这种需要,您确实可以依靠jemalloc来捕获调用 malloc 的位置并将分配可视化为图片或 pdf 多亏jeprof.

当我个人在寻找一种检测原生内存泄漏的方法时,我很快找到了几篇描述大致思路的文章,但找不到任何描述如何一步一步进行的文章,所以我会尝试去做在我的回答中。


添加泄漏端点

当您遇到 dropwizard 应用程序的问题时,让我们创建一个泄漏端点作为示例。

让我们在dropwizard-example 中添加我们的泄漏端点。

  1. 使用以下命令克隆存储库 git clone git@github.com:dropwizard/dropwizard.git
  2. 切换到最后一个标签 git checkout v2.0.13
  3. 添加漏类LeakObjectdropwizard-example/src/main/java/com/example/helloworld/api/
  4. 添加端点类MemoryLeakTestResourcedropwizard-example/src/main/java/com/example/helloworld/resources/
  5. 将端点注册在 dropwizard-example/src/main/java/com/example/helloworld/HelloWorldApplication.java

班上 LeakObject

public class LeakObject {
    private static final byte[] ZIP_CONTENT;

    static {
        try {
            ZIP_CONTENT = Files.readAllBytes(
                Paths.get("target/dropwizard-example-1.0.0-SNAPSHOT.jar")
            );
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public String buildSaying() throws IOException {
        // The native memory leak is caused by the unclosed ZipInputStream
        ZipInputStream inputStream = new ZipInputStream(
            new ByteArrayInputStream(ZIP_CONTENT)
        );
        return String.format(
            "Hello, I'm a leak in native memory %d !", inputStream.read()
        );
    }
}
Run Code Online (Sandbox Code Playgroud)

班上 MemoryLeakTestResource

@Path("/native-memory-leak")
@Produces(MediaType.TEXT_PLAIN)
public class MemoryLeakTestResource {


    private final LeakObject leakObject = new LeakObject();

    @GET
    public String sayHelloWithLeakNative() throws IOException {
        return leakObject.buildSaying();
    }
}
Run Code Online (Sandbox Code Playgroud)

班上 HelloWorldApplication

public class HelloWorldApplication extends Application<HelloWorldConfiguration> {
    ...

    @Override
    public void run(HelloWorldConfiguration configuration, Environment environment) {
        ...
        environment.jersey().register(new MemoryLeakTestResource());
    }
}
Run Code Online (Sandbox Code Playgroud)

构建应用程序

在终端中:

  1. 进入目录 dropwizard-example
  2. 使用mvn clean install -DskipTests.

这将在target调用中创建一个胖罐子dropwizard-example-1.0.0-SNAPSHOT.jar


安装 jemalloc

为了避免与目标环境紧密耦合,让我们使用 docker 来完成这个任务。

要安装jemalloc,我们需要:

  1. 从github下载源
  2. 配置构建使用./configure --enable-prof --enable-debug以启用堆分析和泄漏检测功能
  3. 构建它 make
  4. 安装它 make install

相应的 Dockerfile

FROM openjdk:11-slim

RUN apt-get update && \
    apt-get install -y tcpflow vim htop iotop jq curl gcc make graphviz && \
    curl -O -L https://github.com/jemalloc/jemalloc/releases/download/5.2.1/jemalloc-5.2.1.tar.bz2 && \
    tar jxf jemalloc-*.tar.bz2 && \
    rm jemalloc-*.tar.bz2 && \
    cd jemalloc-* && \
    ./configure --enable-prof --enable-debug && \
    make && \
    make install && \
    cd - && \
    rm -rf jemalloc-*

WORKDIR /root
Run Code Online (Sandbox Code Playgroud)

要构建您的 docker 镜像:

  1. 在终端中,进入包含您的目录 Dockerfile
  2. 然后启动构建命令 docker build -t native-memory-leak .

这将创建一个带有java11的 docker 映像,jemallocjeprof已安装


启动应用程序 jemalloc

为此,您需要:

  1. 将环境变量设置LD_PRELOAD为库的位置libjemalloc.so
  2. 环境变量设置MALLOC_CONFprof_leak:true,prof_final:true以使泄漏的报告,并使其倾倒最终的内存使用情况的文件(根据模式命名<prefix>.<pid>.<seq>.f.heap上的应用程序退出)
  3. 启动您的 Java 应用程序

这些步骤可以使用以下类型的命令完成:

LD_PRELOAD=/usr/local/lib/libjemalloc.so \
    MALLOC_CONF=prof_leak:true,prof_final:true \
    java ...
Run Code Online (Sandbox Code Playgroud)

识别本机内存泄漏的策略 jmalloc

  1. 如前所述启动 Java 应用程序
  2. 启动压力测试
  3. 停止应用
  4. 发射 jeprof

1.启动java应用

我们将在 docker 容器中启动应用程序。

首先,让我们使用下一个命令启动容器:

docker run -it --rm -v $(pwd):/root \
    --name native-memory-leak-test native-memory-leak /bin/bash
Run Code Online (Sandbox Code Playgroud)

此命令将在native-memory-leak-test基于映像调用的容器中启动 bash,该映像native-memory-leak具有dropwizard-example可从/root.

从这个 bash 中,您可以使用前面描述的命令启动应用程序,在我们的例子中:

LD_PRELOAD=/usr/local/lib/libjemalloc.so \
    MALLOC_CONF=prof_leak:true,prof_final:true \
    java -jar target/dropwizard-example-1.0.0-SNAPSHOT.jar server example.yml
Run Code Online (Sandbox Code Playgroud)

应用程序完全启动后,我们可以转到下一部分

2. 启动压力测试

这一步的想法是多次调用导致内存泄漏的端点,以确保泄漏会清楚地出现在最终报告中。

在这种情况下,我们将使用下一个命令从新终端调用端点 2000 次:

docker exec -it native-memory-leak-test \
    /bin/bash -c "for i in {1..2000}; do curl -s localhost:8080/native-memory-leak > /dev/null; done"
Run Code Online (Sandbox Code Playgroud)

此命令将使用curl命令调用端点 2000 次

命令结束后,我们可以进入下一节

3. 停止应用

在我们启动应用程序的容器中,我们可以使用Ctrl+停止它C,将内存使用情况转储到类型为的堆文件中jeprof.<pid>.0.f.heap

然后,您应该在应用程序的标准输出中看到如下内容:

<jemalloc>: Leak approximation summary: ~<total-bytes> bytes, ~<total-objects> objects, >= 37 contexts
<jemalloc>: Run jeprof on "jeprof.<pid>.0.f.heap" for leak detail
Run Code Online (Sandbox Code Playgroud)

这表明 jemalloc 生成了一个名为jeprof.<pid>.0.f.heap.

4. 启动 jeprof

从之前生成的堆文件中,我们将使用jeprof下一个命令生成人类可读的输出。

jeprof --show_bytes --gif $(which java) jeprof.<pid>.0.f.heap > result.gif
Run Code Online (Sandbox Code Playgroud)

此命令将生成一个 gif 文件result.gifdropwizard-examplejeprof.<pid>.0.f.heap感谢jeprof.

生成的 gif 应该是一个分配树,其中主干是“os malloc”,表示 JVM 已分配的内容。您的泄漏(如果有)将与树断开连接,如下例所示:

jeprof 结果示例

在这个例子中,我们可以看到我们有一个由“ Java_java_util_zip_Inflater_init”引起的泄漏,但我们仍然需要在我们的应用程序中识别调用这个本地方法的java代码。

另请参阅用例:泄漏检查


找出泄漏的代码

在这一点上,我们知道我们有一个泄漏,但我们仍然需要找出它在我们的应用程序中的位置。对于这部分,我能找到的最好方法是使用JProfiler(它是一个商业分析器,但您可以使用试用密钥)。

以下是执行步骤:

  1. 在主机上启动 Java 应用程序
  2. 在主机上启动 JProfiler
  3. 单击“附加到正在运行的 JVM”
  4. 选择应用对应的JVM,然后点击“开始”
  5. 选择分析模式“异步采样”
  6. 编辑模式“异步采样”的设置,勾选“启用原生库采样”,然后点击“确定”
  7. “CPU Views”中,点击记录CPU数据
  8. 启动压力测试
  9. “CPU 视图”中,转到“调用树”
  10. 单击菜单项“查看” / “查找”以输入要查找的本机方法的 FQN(在我们的示例中为“ Java_java_util_zip_Inflater_init”)

使用这种方法,我可以在下一个屏幕截图中看到我的泄漏com.example.helloworld.api.LeakObject.buildSaying按预期发生在方法中。

JProfiler CPU 视图


con*_*ion 3

寻找从 JNI 库分配的内存泄漏比寻找堆上的内存泄漏更加痛苦。您可能对该工具jemalloc感兴趣,因为它可以帮助隔离大量分配来自本机内存分配的位置。然后,您可以使用该jeprof工具生成图形来可视化发生重要分配的堆栈。

查看这些与您的问题非常相似的文章:

他们详细介绍了查找由 JNI 库(即内存不在堆上)引起的内存泄漏的过程。