首页 > 解决方案 > 我实际上想要对 linux 命名管道的阻塞写入

问题描述

我有一个线程应用程序。在一个线程中,我想将从数据库流式传输的数据写入命名管道,并且如果阅读器(在这种情况下为命令行程序“zip”)无法跟上 java 线程,我希望写入阻塞。进入单个 Zip 文件条目的数据可能大于系统的主内存。

无论在写入命名管道时是否使用 FileOutputStream 或 FileWriter,我都会看到以下行为:写入将缓冲,直到 java 堆被填满,然后实际上将线程减慢到读取器的速度。对于单线程进程,这只是浪费空间,但对于多线程进程,这会让其他线程运行到内存不足异常中。

我看到的唯一剩下的选择是使用 JNA 在 C 中进行阻塞写入。欢迎提出其他建议。

顺便提一句。我让“zip”工具进行压缩的真正原因是 java.util.zip 和 Lingala 的 Zip4J 会用缓冲区填充 RAM。

好的,这是一个精简的小例子。我使用“mkfifo fifo”创建了一个命名管道“fifo”并启动“zip --fifo -fz -v fifo.zip fifo”以在命名管道上设置读取过程块。然后我用 -Xmx32M 启动以下 java 程序。如果没有“Mem Eater”线程,它的行为如上所述。有了它,这个线程将在 OutOfMemoryException 中运行。现在看代码:

import java.io.FileWriter;
import java.util.LinkedList;
import java.util.List;

public class fos {

    public static void main(String[] argv) {
        if (argv.length != 1) {
            System.err.println("Usage is:");
            System.err.println("java fos.java <fifo>");
            System.exit(-1);
        }

        String fifoName = argv[0];
        
        startMemConsumerThread();

        try (var fifoWriter = new FileWriter(fifoName)) {
            for(long i=0L; i< Long.MAX_VALUE; i++)
                fifoWriter.write("Hello World! "+i+"\r\n");
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }

    private static void startMemConsumerThread() {
        final int NUM_CHUNKS = 20;
        final int CHUNK_SIZE = 1024*1024;
        final List< byte[] > chunks = new LinkedList<>();
        
        var t = new Thread("Mem Eater") {
            @Override
            public void run() {
                while (true) {
                    while (chunks.size() < NUM_CHUNKS)
                        chunks.add(new byte[CHUNK_SIZE]);
                    chunks.remove(NUM_CHUNKS % 7);
                }
            }
        };
        
        t.setDaemon(true);
        t.start();
    }
}

标签: javalinuxnamed-pipes

解决方案


我围绕打开、写入、关闭做了一个薄 JNA 包装器,以实现写入的阻塞行为。现在我不再观察到无限的内存消耗,但 Java 堆将增长到配置的最大值,并且 GC 会优雅地处理它。

我必须纠正自己:即使使用 FileWriter.write 也会发生阻塞并且内存消耗是有限的。感谢joni推我!

但是在这个用例中使用 java.util.ZipOutputStream 时,事情就会失控。不过那是另一回事了...


推荐阅读