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

如何解决Java应用程序中使用的OpenCV JNI库中的内存使用跟踪工具

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

在我的Java dropwizard服务器中,我使用JNI opencv绑定。服务器启动时,Java堆内存似乎并没有增加到超过GB,而是由GC定期释放。但是,大量(4-5 GB)的内存正在添加到java进程中,不确定该内存来自何处。

如何跟踪在JNI库中分配的内存并确定是否有泄漏。

解决方法

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

当我个人在寻找一种检测本机内存泄漏的方法时,我很快找到了几篇描述该思想的文章,但是找不到任何逐步描述该思想的文章,因此我将尝试在我的答案中做到这一点。


添加泄漏的端点

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

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

  1. 使用git clone git@github.com:dropwizard/dropwizard.git
  2. 克隆存储库
  3. 使用git checkout v2.0.13切换到最后一个标签
  4. LeakObject中添加泄漏类dropwizard-example/src/main/java/com/example/helloworld/api/
  5. MemoryLeakTestResource中添加端点类dropwizard-example/src/main/java/com/example/helloworld/resources/
  6. 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()
        );
    }
}

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();
    }
}

HelloWorldApplication

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

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

构建应用程序

在终端机中:

  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. 进行构建
  5. 通过make install
  6. 安装

相应的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映像:

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

这将创建一个安装了java 11,jemallocjeprof的docker镜像


使用jemalloc启动应用程序

为此,您将需要:

  1. 将环境变量LD_PRELOAD设置为库libjemalloc.so的位置
  2. 将环境变量MALLOC_CONF设置为prof_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 ...

使用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

此命令将根据图像native-memory-leak-test在名为native-memory-leak的容器中启动bash,其中包含{{}提供的当前目录(我们假定为dropwizard-example)的内容。 1}}。

通过此bash,您可以使用前面描述的命令启动应用程序,在本例中将是这样:

/root

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

2。启动压力测试

在此步骤中,想法是多次调用导致内存泄漏的端点,以确保泄漏将清楚地显示在最终报告中。

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

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

此命令将使用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" 命令调用端点的2000次

命令结束后,我们可以转到下一部分

3。停止应用程序

在启动应用程序的容器中,我们可以使用 Ctrl + C 将其停止使用,以将内存使用情况转储到{{1}类型的堆文件中}

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

curl

这表明jemalloc已生成一个名为jeprof.<pid>.0.f.heap的堆文件。

4。启动<jemalloc>: Leak approximation summary: ~<total-bytes> bytes,~<total-objects> objects,>= 37 contexts <jemalloc>: Run jeprof on "jeprof.<pid>.0.f.heap" for leak detail

从先前生成的堆文件中,我们将使用jeprof.<pid>.0.f.heap和下一条命令来生成易于阅读的输出。

jeprof

由于jeprof,此命令将从jeprof --show_bytes --gif $(which java) jeprof.<pid>.0.f.heap > result.gif result.gif中生成一个名为dropwizard-example的gif文件。

生成的gif应该是分配树,其中的主干为“ os malloc” ,代表JVM分配了什么。您的泄漏(如果有的话)将与树断开连接,如下例所示:

Example of jeprof result

在此示例中,我们可以看到由“ jeprof.<pid>.0.f.heap”引起的泄漏,但是我们仍然需要在应用程序中标识调用此本机方法的Java代码。

另请参阅Use Case: Leak Checking


发现泄漏代码

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

这是要执行的步骤:

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

使用这种方法,我可以在下一个屏幕快照中看到我的泄漏发生在预期的jeprof方法中。

JProfiler CPU Views

,

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

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

他们详细介绍了由JNI库(即,堆上的内存不是 )导致的内存泄漏的恢复过程。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其他元素将获得点击?
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。)
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbcDriver发生异常。为什么?
这是用Java进行XML解析的最佳库。
Java的PriorityQueue的内置迭代器不会以任何特定顺序遍历数据结构。为什么?
如何在Java中聆听按键时移动图像。
Java“Program to an interface”。这是什么意思?
Java在半透明框架/面板/组件上重新绘画。
Java“ Class.forName()”和“ Class.forName()。newInstance()”之间有什么区别?
在此环境中不提供编译器。也许是在JRE而不是JDK上运行?
Java用相同的方法在一个类中实现两个接口。哪种接口方法被覆盖?
Java 什么是Runtime.getRuntime()。totalMemory()和freeMemory()?
java.library.path中的java.lang.UnsatisfiedLinkError否*****。dll
JavaFX“位置是必需的。” 即使在同一包装中
Java 导入两个具有相同名称的类。怎么处理?
Java 是否应该在HttpServletResponse.getOutputStream()/。getWriter()上调用.close()?
Java RegEx元字符(。)和普通点?