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

标签 java opencv memory-management memory-leaks java-native-interface

有哪些工具可用于跟踪 Java 应用程序中 Opencv JNI 库分配的内存。
在我的 java dropwizard 服务器中,我使用 JNI opencv 绑定(bind)。当服务器打开时,java 堆内存似乎没有增加超过 GB,它会定期被 GC 释放。但是巨大的(4-5 GB)内存被添加到java进程中,不确定它来自哪里。
如何跟踪 JNI 库中分配的内存并识别是否有泄漏。

最佳答案

@concision 的回答是正确的,对于这样的需求,你确实可以依靠jemalloc感谢 jeprof 捕获 malloc 被调用的位置并将分配可视化为图片或 pdf .
当我个人在寻找一种检测原生内存泄漏的方法时,我很快找到了几篇描述大致思路的文章,但找不到任何描述如何一步一步进行的文章,所以我会尝试去做在我的回答中。

添加泄漏端点
当您使用 dropwizard 应用程序遇到问题时,让我们创建一个泄漏端点作为示例。
让我们在 dropwizard-example 中添加我们的泄漏端点.

  • 使用 git clone git@github.com:dropwizard/dropwizard.git 克隆存储库
  • 使用 git checkout v2.0.13 切换到最后一个标签
  • 添加泄漏类 LeakObjectdropwizard-example/src/main/java/com/example/helloworld/api/
  • 添加端点类 MemoryLeakTestResourcedropwizard-example/src/main/java/com/example/helloworld/resources/
  • dropwizard-example/src/main/java/com/example/helloworld/HelloWorldApplication.java 中注册端点

  • 类(class)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()
            );
        }
    }
    
    类(class)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();
        }
    }
    
    类(class)HelloWorldApplication
    public class HelloWorldApplication extends Application<HelloWorldConfiguration> {
        ...
    
        @Override
        public void run(HelloWorldConfiguration configuration, Environment environment) {
            ...
            environment.jersey().register(new MemoryLeakTestResource());
        }
    }
    

    构建应用程序
    在终端中:
  • 进入目录dropwizard-example
  • 使用 mvn clean install -DskipTests 构建项目.

  • 这将在 target 中创建一个 fat jar 子叫 dropwizard-example-1.0.0-SNAPSHOT.jar
    安装 jemalloc为了避免与目标环境紧耦合,让我们使用 docker 来完成这个任务。
    安装 jemalloc , 我们需要:
  • 从github下载源
  • 使用 ./configure --enable-prof --enable-debug 配置构建为了启用堆分析和泄漏检测功能
  • 使用 make 构建它
  • 使用 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
    
    要构建您的 docker 镜像:
  • 在终端中,进入包含您的 Dockerfile 的目录。
  • 然后启动构建命令 docker build -t native-memory-leak .

  • 这将创建一个带有 java 的 docker 镜像11、jemallocjeprof已安装

    使用 jemalloc 启动应用程序
    为此,您需要:
  • 设置环境变量 LD_PRELOAD到图书馆的位置libjemalloc.so
  • 设置环境变量 MALLOC_CONFprof_leak:true,prof_final:true为了启用泄漏报告并使其在应用程序退出
  • 时将最终内存使用转储到文件(根据模式 <prefix>.<pid>.<seq>.f.heap 命名)
  • 启动您的 Java 应用程序

  • 这些步骤可以使用以下类型的命令完成:
    LD_PRELOAD=/usr/local/lib/libjemalloc.so \
        MALLOC_CONF=prof_leak:true,prof_final:true \
        java ...
    

    使用 jmalloc 识别 native 内存泄漏的策略
  • 如前所述启动 java 应用程序
  • 启动压力测试
  • 停止应用
  • 启动 jeprof

  • 1.启动java应用
    我们将在 docker 容器中启动应用程序。
    首先,让我们使用下一个命令启动容器:
    docker run -it --rm -v $(pwd):/root \
        --name native-memory-leak-test native-memory-leak /bin/bash
    
    此命令将在名为 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
    
    应用程序完全启动后,我们可以转到下一部分
    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"
    
    此命令将使用 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
    
    这表明 jemalloc 生成了一个名为 jeprof.<pid>.0.f.heap 的堆文件。 .
    4.启动jeprof从之前生成的堆文件中,我们将使用 jeprof使用下一个命令生成人类可读的输出。
    jeprof --show_bytes --gif $(which java) jeprof.<pid>.0.f.heap > result.gif
    
    此命令将生成一个名为 result.gif 的 gif 文件。在 dropwizard-example来自 jeprof.<pid>.0.f.heap感谢 jeprof .
    生成的 gif 应该是一个分配树,其中主干是 "os malloc"表示 JVM 已分配的内容。您的泄漏(如果有)将与树断开连接,如下例所示:
    Example of jeprof result
    在这个例子中,我们可以看到我们有一个由“Java_java_util_zip_Inflater_init”引起的泄漏,但我们仍然需要在我们的应用程序中识别调用这个本地方法的java代码。
    另见 Use Case: Leak Checking

    找出泄漏的代码
    在这一点上,我们知道我们有一个泄漏,但我们仍然需要找出它在我们的应用程序中的位置。对于这部分,我能找到的最好方法是使用 JProfiler (它是一个商业分析器,但您可以使用试用 key )。
    以下是执行步骤:
  • 在主机上启动 Java 应用程序
  • 在您的主机上启动您的 JProfiler
  • 单击“附加到正在运行的 JVM”
  • 选择应用对应的JVM,然后点击“开始”
  • 选择分析模式“异步采样”
  • 编辑“异步采样”模式的设置,勾选“启用原生库采样”,然后点击“确定”
  • 在“CPU Views”中,点击记录CPU数据
  • 启动压力测试
  • 在“CPU View ”中,转到“调用树”
  • 单击菜单项“查看”/“查找”以输入要查找的 native 方法的 FQN(在我们的示例中为“Java_java_util_zip_Inflater_init”)

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

    关于java - 在 Java 应用程序中使用的 OpenCV JNI 库中的内存使用跟踪工具,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64117731/

    相关文章:

    java - @Scheduled with cron 表达式突然停止工作

    java - 将 sql varchar 分配给 JLabel 值

    c - 图像修复/cvRectangle()

    c++ - findChessboardCorners 校准图像失败

    c++ - 包含 vector 的指针列表的内存管理

    java - 尝试在 java 中调用函数,提示 ".class expected"。

    python - 在 OpenCv、Python 中保存相同图像时无法获得与原始图像相同的颜色

    linux -/proc/$pid/maps 显示在 x86_64 linux 上没有 rwx 权限的页面

    memory - 为什么rabbitmq上的二进制内存使用量会增加

    java - Eclipse 所有作业监听器