Linux 上的 FileChannel.write 会产生大量垃圾,但在 Mac 上不会

Posted

技术标签:

【中文标题】Linux 上的 FileChannel.write 会产生大量垃圾,但在 Mac 上不会【英文标题】:FileChannel.write on Linux produces lots of garbage, but not on Mac 【发布时间】:2011-09-19 17:19:49 【问题描述】:

我试图限制我的日志库产生的垃圾量,所以我编写了一个测试来显示 FileChannel.write 创建了多少内存。下面的代码在我的 Mac 上分配零内存,但在我的 Linux 机器(Ubuntu 10.04.1 LTS)上创建了大量垃圾,触发了 GC。 FileChannels 应该是快速和轻量级的。有没有在 Linux 上改进的 JRE 版本?

    File file = new File("fileChannelTest.log");
    FileOutputStream fos = new FileOutputStream(file);
    FileChannel fileChannel = fos.getChannel();
    ByteBuffer bb = ByteBuffer.wrap("This is a log line to test!\n".getBytes());
    bb.mark();
    long freeMemory = Runtime.getRuntime().freeMemory();
    for (int i = 0; i < 1000000; i++) 
        bb.reset();
        fileChannel.write(bb);
    
    System.out.println("Memory allocated: " + (freeMemory - Runtime.getRuntime().freeMemory()));

我的 JRE 的详细信息如下:

java version "1.6.0_19"
Java(TM) SE Runtime Environment (build 1.6.0_19-b04)
Java HotSpot(TM) 64-Bit Server VM (build 16.2-b04, mixed mode)

更新到:

java version "1.6.0_27"
Java(TM) SE Runtime Environment (build 1.6.0_27-b07)
Java HotSpot(TM) 64-Bit Server VM (build 20.2-b06, mixed mode)

而且效果很好。 :-|

好吧,现在我们知道 FileChannelImpl 的早期版本存在内存分配问题。

【问题讨论】:

当我使用 java 1.6.0_26 在 linux 上运行它时,它说分配的内存是“0”。 这里相同:但我在 11.04。抱歉,我没有要测试的 10.04。 顺便说一句,我能够编写一个日志库,在日志记录到磁盘时分配零内存。更多细节在这里:mentalog.soliveirajr.com 【参考方案1】:

我使用的是 Ubuntu 10.04,我可以确认您的观察。我的 JDK 是:

    java version "1.6.0_20"
    OpenJDK Runtime Environment (IcedTea6 1.9.9) (6b20-1.9.9-0ubuntu1~10.04.2)
    OpenJDK 64-Bit Server VM (build 19.0-b09, mixed mode)

解决方案是使用DirectByteBuffer,而不是由数组支持的HeapByteBuffer

如果我没记错的话,这是一个可以追溯到 JDK 1.4 的非常古老的“功能”:如果您不将 DirectByteBuffer 分配给 Channel,则分配一个临时的 DirectByteBuffer 并复制内容在写作之前。您基本上会看到这些临时缓冲区在 JVM 中徘徊。

以下代码适用于我:

    File file = new File("fileChannelTest.log");
    FileOutputStream fos = new FileOutputStream(file);
    FileChannel fileChannel = fos.getChannel();

    ByteBuffer bb1 = ByteBuffer.wrap("This is a log line to test!\n".getBytes());

    ByteBuffer bb2 = ByteBuffer.allocateDirect(bb1.remaining());
    bb2.put(bb1).flip();

    bb2.mark();
    long freeMemory = Runtime.getRuntime().freeMemory();
    for (int i = 0; i < 1000000; i++) 
        bb2.reset();
        fileChannel.write(bb2);
    
    System.out.println("Memory allocated: " + (freeMemory - Runtime.getRuntime().freeMemory()));

仅供参考:HeapByteBuffer的副本摄于

    sun.nio.ch.IOUtil.write(FileDescriptor, ByteBuffer, long, NativeDispatcher, Object)

使用sun.nio.ch.Util.getTemporaryDirectBuffer(int)。这反过来使用SoftReferences 实现了一个小的DirectByteBuffers 的每个线程池。所以没有真正的内存泄漏,但只有浪费。 叹息

【讨论】:

堆缓冲区的主要问题是整个 buffer.remaining() 每次都被复制,向套接字发送巨大的缓冲区就是这样。无论如何,没有必要使用带有 NIO 的堆缓冲区,也没有必要使用带有 SSLEngine 的直接缓冲区。

以上是关于Linux 上的 FileChannel.write 会产生大量垃圾,但在 Mac 上不会的主要内容,如果未能解决你的问题,请参考以下文章

Windows 上的 WaitOnAddress() 在 Linux 上的完全等价物是啥?

markdown [linux:NFS] Linux上的网络文件系统。 #linux

Linux 上的 JavaFX

怎样彻底删除linux上的uwsgi

使用VS2012编写arm-linux上的应用程序

使用EditPlus编辑Linux上的文本文件