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

huangapple go评论79阅读模式
英文:

Tool for Memory Use Tracking in OpenCV JNI library used in Java Application

问题

以下是您要翻译的内容:

什么工具可用于跟踪Java应用程序中由OpenCV JNI库分配的内存。

在我的Java Dropwizard服务器中,我使用JNI OpenCV绑定。当服务器运行时,Java堆内存似乎不会超过1 GB,它会定期由垃圾回收器释放。但是大量(4-5 GB)的内存被添加到Java进程中,不确定它是从哪里来的。

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

英文:

What are the tools available to track memory allocated by Opencv JNI library in Java Application.

In my java dropwizard server, I use JNI opencv bindings. While the server is on, java heap memory does not seem to be increasing beyond a GB, it is regularly freed by GC. But huge (4-5 GB) amount of memory is being added to the java process, not sure where it is coming from.

How can the memory allocated in JNI library be tracked and identify if any leaks.

答案1

得分: 7

以下是翻译的内容:


添加有漏洞的端点

由于您在dropwizard应用程序中遇到问题,我们将创建一个有漏洞的端点作为示例。

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

  1. 使用 git clone git@github.com:dropwizard/dropwizard.git 克隆存储库
  2. 使用 git checkout v2.0.13 切换到最新标签
  3. dropwizard-example/src/main/java/com/example/helloworld/api/ 中添加有漏洞的类 LeakObject
  4. dropwizard-example/src/main/java/com/example/helloworld/resources/ 中添加端点类 MemoryLeakTestResource
  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 {
        // 本地内存泄漏是由未关闭的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 的可执行 JAR 文件。


安装 jemalloc

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

要安装 jemalloc,我们需要:

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

相应的 Dockerfile

FROM openjdk:11-slim

ARG JEMALLOC_VERSION=5.3.0

RUN apt-get update && \
    apt-get install -y tcpflow vim htop iotop jq curl gcc make graphviz bzip2 && \
    curl -O -L https://github.com/jemalloc/jemalloc/releases/download/$JEMALLOC_VERSION/jemalloc-$JEMALLOC_VERSION.tar.bz2 && \
    tar xjvf 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 ...

jemalloc 策略识别本地内存泄漏

  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),可从 /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. 启动压力测试

在此步骤中,我们的想法是多次调用导致内存泄漏的端点,以确保泄漏将在最

英文:

The response of @concision is correct, for such need, you can indeed rely on jemalloc to capture where malloc is called and visualize the allocations as a picture or pdf thanks to jeprof.

When I was personally looking for a way to detect a native memory leak, I quickly found several articles that describe the idea in general but I could not find any article that describes how to do it step by step, so I will try to do it in my answer.


Add the leaky endpoint

As you meet your issue with a dropwizard application, let's create a leaky endpoint as example.

Let's add our leaky endpoint in dropwizard-example.

  1. Clone the repository with git clone git@github.com:dropwizard/dropwizard.git
  2. Switch to the last tag with git checkout v2.0.13
  3. Add the leaky class LeakObject in dropwizard-example/src/main/java/com/example/helloworld/api/
  4. Add the endpoint class MemoryLeakTestResource in dropwizard-example/src/main/java/com/example/helloworld/resources/
  5. Register the endpoint in dropwizard-example/src/main/java/com/example/helloworld/HelloWorldApplication.java

The class LeakObject

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

    static {
        try {
            ZIP_CONTENT = Files.readAllBytes(
                Paths.get(&quot;target/dropwizard-example-1.0.0-SNAPSHOT.jar&quot;)
            );
        } 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(
            &quot;Hello, I&#39;m a leak in native memory %d !&quot;, inputStream.read()
        );
    }
}

The class MemoryLeakTestResource

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


    private final LeakObject leakObject = new LeakObject();

    @GET
    public String sayHelloWithLeakNative() throws IOException {
        return leakObject.buildSaying();
    }
}

The class HelloWorldApplication

public class HelloWorldApplication extends Application&lt;HelloWorldConfiguration&gt; {
    ...

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

Build the application

In a terminal:

  1. Go to the directory dropwizard-example
  2. Build the project with mvn clean install -DskipTests.

This will create a fat jar in target called dropwizard-example-1.0.0-SNAPSHOT.jar


Install jemalloc

To avoid being tightly coupled with the target environment, let's use docker for this task.

To install jemalloc, we need to:

  1. Download the sources from github
  2. Configure the build using ./configure --enable-prof --enable-debug in order to enable the heap profiling and the leak detection functionality
  3. Build it with make
  4. Install it with make install

The corresponding Dockerfile

FROM openjdk:11-slim

ARG JEMALLOC_VERSION=5.3.0

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

WORKDIR /root

To build your docker image:

  1. In a terminal, go in the directory that contains your Dockerfile
  2. Then launch the build command docker build -t native-memory-leak .

This will create a docker image with java 11, jemalloc and jeprof installed


Launch the application with jemalloc

For this you will need to:

  1. Set the environment variable LD_PRELOAD to the location of the library libjemalloc.so
  2. Set the environment variable MALLOC_CONF to prof_leak:true,prof_final:true in order to enable the leak reporting and to make it dump the final memory usage to a file (named according to the pattern &lt;prefix&gt;.&lt;pid&gt;.&lt;seq&gt;.f.heap) on application exit
  3. Launch your java application

Those steps can be done with a command of type:

LD_PRELOAD=/usr/local/lib/libjemalloc.so \
    MALLOC_CONF=prof_leak:true,prof_final:true \
    java ...

Strategy to identify a native memory leak with jmalloc

  1. Launch the java application as described previously
  2. Launch your stress test
  3. Stop the application
  4. Launch jeprof

1. Launch the java application

We will launch the application within a docker container.

So first, let's start the container with the next command:

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

This command will launch a bash in a container called native-memory-leak-test based on the image native-memory-leak with the content of the current directory (that we assume to be dropwizard-example) available from /root.

From this bash, you can launch the application using the command described previously which will be in our case:

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

Once the application is fully started, we can go to the next section

2. Launch your stress test

In this step, the idea is to call several times the endpoint that causes the memory leak to ensure that the leak will clearly appear in the final report.

In this case, we will call 2000 times the endpoint from a new terminal with the next command:

docker exec -it native-memory-leak-test \
    /bin/bash -c &quot;for i in {1..2000}; do curl -s localhost:8080/native-memory-leak &gt; /dev/null; done&quot;

This command will call 2000 times the endpoint using a curl command

Once the command is over, we can go to the next section

3. Stop the application

In the container in which we launched the application, we can stop it using <kbd>Ctrl</kbd>+<kbd>C</kbd> to dump the memory usage into a heap file of type jeprof.&lt;pid&gt;.0.f.heap

You should then see in the standard output of the application something like:

&lt;jemalloc&gt;: Leak approximation summary: ~&lt;total-bytes&gt; bytes, ~&lt;total-objects&gt; objects, &gt;= 37 contexts
&lt;jemalloc&gt;: Run jeprof on &quot;jeprof.&lt;pid&gt;.0.f.heap&quot; for leak detail

This indicates that jemalloc has generated a heap file whose name is jeprof.&lt;pid&gt;.0.f.heap.

4. Launch jeprof

From the previously generated heap file, we will use jeprof to generate a human-readable output with the next command.

jeprof --show_bytes --gif $(which java) jeprof.&lt;pid&gt;.0.f.heap &gt; result.gif

This command will generate a gif file called result.gif in dropwizard-example from jeprof.&lt;pid&gt;.0.f.heap thanks to jeprof.

The resulting gif should be a tree of allocations where the trunk is "os malloc" representing what has been allocated by the JVM. Your leak, if any, will be disconnected from the tree like in the next example:

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

In this example, we can see that we have a leak caused by "Java_java_util_zip_Inflater_init" but we still need to identify the java code that calls this native method in our application.

See also Use Case: Leak Checking


Spot the leaky code

At this point, we know that we have a leak but we still need to spot where it is located in our application. For this part, the best approach I could found, is to use JProfiler (it is a commercial profiler but you can use a trial key).

Here are the steps to do:

  1. Launch your Java application on your host
  2. Launch your JProfiler on your host
  3. Click on "Attach to a running JVM"
  4. Select the JVM corresponding to the application, then click "Start"
  5. Choose the profiling mode "Async sampling"
  6. Edit the settings of the mode "Async sampling" to check “Enable sampling of native libraries”, then click "OK"
  7. In "CPU Views", click to record CPU Data
  8. Launch your stress test
  9. In "CPU Views", go to "Call Tree"
  10. Click on the menu item "View" / "Find" to input the FQN of the native method to find (in our case it would be "Java_java_util_zip_Inflater_init")

With this approach, I can see in the next screenshot that my leak occurs in the method com.example.helloworld.api.LeakObject.buildSaying as expected.

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

答案2

得分: 3

从JNI库分配的内存泄漏追踪起来比追踪堆内存泄漏要痛苦一些。工具jemalloc可能会引起您的兴趣,因为它可以帮助隔离出原生内存分配中大量分配来自哪里。然后,您可以使用jeprof工具生成图形来可视化发生重要分配的堆栈。

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

它们详细介绍了追踪内存泄漏的过程,这是由JNI库引起的(即内存不在堆上)。

英文:

Hunting down memory leaks allocated from JNI libraries is a bit more of a painful process than hunting memory leaks on the heap. The tool jemalloc might interest you, as it can help isolate where a large amount of allocations are coming from in native memory allocations. You can then use the jeprof tool to generate a graphic to visualize the stack where significant allocations happen.

Take a look at these articles with issues that are very similar to yours:

They detail the process of hunting down their memory leak, caused by a JNI library (i.e. memory was not on the heap).

huangapple
  • 本文由 发表于 2020年9月29日 18:27:38
  • 转载请务必保留本文链接:https://go.coder-hub.com/64117731.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定