从DirectMemory谈谈Java NIO

原创 2015年06月28日 00:20:06

本机内存DirectMemory,属于C Heap,可以通过参数-XX:MaxDirectMemorySize指定。
如果不指定,该参数的默认值为Xmx的值减去1个Survior区的值。如设置启动参数-Xmx20M -Xmn10M -XX:SurvivorRatio=8,那么申请20M-1M=19M的DirectMemory是没有问题的。

/*VM Args: -Xmx20M -Xmn10M -XX:MaxDirectMemorySize=10M*/
public class DirectMemoryOOM {

    private static final int _1MB = 1024 * 1024;

    public static void main(String[] args){
        ByteBuffer.allocateDirect(11*_1MB);
    }
}

Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory
at java.nio.Bits.reserveMemory(Bits.java:658)
at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:123)
at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:311)
at DirectMemoryOOM.main(DirectMemoryOOM.java:16)

修改上面的代码:

/*VM Args: -Xmx20M -Xmn10M -XX:MaxDirectMemorySize=10M -XX:+PrintGCDetails*/
public class DirectMemoryOOM {

    private static final int _1MB = 1024 * 1024;

    public static void main(String[] args){
        ByteBuffer.allocateDirect(10*_1MB);
        ByteBuffer.allocateDirect(_1MB);
    }
}

[GC (System.gc()) [PSYoungGen: 983K->632K(9216K)] 983K->640K(19456K), 0.0039667 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (System.gc()) [PSYoungGen: 632K->0K(9216K)] [ParOldGen: 8K->505K(10240K)] 640K->505K(19456K), [Metaspace: 2520K->2520K(1056768K)], 0.0073120 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
Heap
 PSYoungGen      total 9216K, used 82K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
  eden space 8192K, 1% used [0x00000000ff600000,0x00000000ff614968,0x00000000ffe00000)
  from space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
  to   space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
 ParOldGen       total 10240K, used 505K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  object space 10240K, 4% used [0x00000000fec00000,0x00000000fec7e6c8,0x00000000ff600000)
 Metaspace       used 2527K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 271K, capacity 386K, committed 512K, reserved 1048576K

先申请10M,再申请1M,此时会发现JVM不会出现OOM的现象。

可以发现发生了一个FullGC,FullGC后面的system关键字代表这一次FullGC是由System.gc()引起的。

于是,跟踪源码,在分配内存时,间接调用了java.nio.Bits类的reserveMemory方法,而该方法体中显示调用了System.gc():

这里需要说明的是,如果DirectByteBuffer的空间够用,那么System.gc()是不会触发FullGC的。

而如果使用了参数-XX:+DisableExplicitGC,那么调用System.gc()时也不会触发FullGC。

修改代码:

ByteBuffer byteBuffer = ByteBuffer.allocateDirect(10*_1MB);
byteBuffer.allocateDirect(_1MB);

Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory
at java.nio.Bits.reserveMemory(Bits.java:658)
at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:123)
at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:311)
at DirectMemoryOOM.main(DirectMemoryOOM.java:16)

说明DirectBuffer的GC规则与堆对象的回收规则是一样的,只有垃圾对象才会被回收,而判定是否为垃圾对象依然是根据引用树中的存活节点来判定。

在垃圾收集时,虽然虚拟机会对DirectMemory进行回收,但是DirectMemory却不像新生代和老年代那样,发现空间不足了就通知收集器进行垃圾回收,它只能等待老年代满了后FullGC,然后“顺便地”帮它清理掉内存中废弃的对象。否则,只能等到抛出内存溢出异常时,在catch块里调用System.gc()。

难道DirectBuffer只有等到它满的时候才能回收吗?其实可以通过下面的代码主动回收:

ByteBuffer byteBuffer = ByteBuffer.allocateDirect(10*_1MB);
((DirectBuffer)byteBuffer).cleaner().clean(); //释放内存
byteBuffer.allocateDirect(_1MB);

在了解了DirectMemory后,下面我们看看Java NIO里是如何使用它的。

NIO提供了一种将文件映射到内存的方法进行I/O操作,是内容映射模式,这个操作由FileChannel.map()方法实现。

MappedByteBuffer使用FileChannel的map方法,将文件映射到内存:

public abstract MappedByteBuffer map(MapMode mode,
                                     long position, long size)
    throws IOException;

NIO的Buffer还提供了一个可以直接访问系统物理内存,而不需要进行用户态和内核态之间的拷贝,即Direct Memory的类-DirectByteBuffe,是Direct I/O模式。
通过byteBuffer.allocateDirect(capacity)在DirectMemory上进行分配。

Buffer

Buffer有四个状态量: mask,position,limit和capacity

mask,the index to which its position will be reset when the reset method is invoked。就是说当reset方法调用后,buffer的position将会被置为mask的值,如果mask存在的话。

position,当前缓存区的位置

limit,缓存区的实际上限

capacity,缓存区的总容量上限

Buffer的几个方法:
clear,makes a buffer ready for a new sequence of channel-read or relative put operations: It sets the limit to the capacity and the position to zero.

就是说要往buffer写数据时,先执行buffer.clear(),使position=0,limit=capacity

flip,makes a buffer ready for a new sequence of channel-write or relative get operation:It sets the limit to the current position and then sets the position to zero.

就是说当从buffer中读数据写入channel时,先执行flip(),使limit=position,position=0

rewind,makes a buffer ready for re-reading the data that it already contains:It leaves the limit unchanged and sets the position to zero.

当要重新读取buffer中的内容时,先执行rewind(),将position=0.否则报java.nio.BufferUnderflowException异常

 public ByteBuffer get(byte[] dst, int offset, int length) {
    checkBounds(offset, length, dst.length);
    if (length > remaining())
        throw new BufferUnderflowException();
    int end = offset + length;
    for (int i = offset; i < end; i++)
        dst[i] = get();
    return this;
}
 public final int remaining() {
    return limit - position;
}

reset,resets this buffer’s position to the previously-marked position.

Channel - A channel for reading,writing,mapping,and manipulating a file.

A file channel is created by invoking one of the {@link #open open}
methods defined by this class. A file channel can also be obtained from an
existing {@link java.io.FileInputStream#getChannel FileInputStream}, {@link
java.io.FileOutputStream#getChannel FileOutputStream}, or {@link
java.io.RandomAccessFile#getChannel RandomAccessFile} object by invoking
that object’s getChannel method, which returns a file channel that
is connected to the same underlying file.

Channel是一个通道,网络数据通过Channel读取和写入。通道与流的不同之处在于通道是双向的,流只是在一个方向上移动(一个流必须是InputStream或者OutputStream的之类),而通道可以用于读、写或者两者同时进行。

因为Channel是全双工的,所以它可以比流更好的映射底层操作系统API。特别是在Unix网络编程模型中,底层操作系统的通道都是全双工的,同时支持读写操作。

实际上,Channel可以分为两大类:用于网络读写的SelectableChannel和用于文件操作的FileChannel。

应用程序中,不能直接对Channel进行读写操作,而必须通过Buffer来进行:

与Socket和ServerSocket类相对性,NIO提供了SocketChannel和ServerSocketChannel,支持阻塞和非阻塞两种模式。

相关文章推荐

JAVA NIO之浅谈内存映射文件原理与DirectMemory

JAVA类库中的NIO包相对于IO 包来说有一个新功能是内存映射文件,日常编程中并不是经常用到,但是在处理大文件时是比较理想的提高效率的手段。本文我主要想结合操作系统中(OS)相关方面的知识介绍一下原...

JVM的DirectMemory设置

几台服务器的JVM占用内存总是持续增长,大大超过-Xmx设定的值,服务器物理内存几乎被耗尽。 使用jmap查看JVM的内存使用,发现jvm的堆大小完全在-Xmx参数设定的范围之内,那问题只能处在...
  • zshake
  • zshake
  • 2015年07月07日 10:02
  • 2078

Raft算法国际论文全翻译

最近在开发强一致性的分布式算法,因此需要深入理解下Raft算法,这里对Raft论文进行了翻译,留以备用 - Sunface. 英文版论文:https://ramcloud.atlassian.net/...

解释执行的语言相比编译执行的语言优缺点思考

编译执行的语言例如C语言,必须经过编译器编译转成机器指令才能被计算机执行,编译检查词法、语法、代码优化、生成代码和可执行文件需要时间,这是其中一个缺点。解释执行的语言就没有这个缺点,例如shell脚本...

JAVA NIO之浅谈内存映射文件原理与DirectMemory

JAVA类库中的NIO包相对于IO 包来说有一个新功能是内存映射文件,日常编程中并不是经常用到,但是在处理大文件时是比较理想的提高效率的手段。本文我主要想结合操作系统中(OS)相关方面的知识介绍一下原...

[Java][IO]JAVA NIO之浅谈内存映射文件原理与DirectMemory

JAVA NIO之浅谈内存映射文件原理与DirectMemory JAVA类库中的NIO包相对于IO 包来说有一个新功能是内存映射文件,日常编程中并不是经常用到,但是在处理大文件时是比较理想的提高效...

NIO--JAVA NIO之浅谈内存映射文件原理与DirectMemory

转载自:http://www.360doc.com/content/13/0502/23/7669533_282552666.shtml  JAVA类库中的NIO包相对于IO 包来说有一个...

基于java NIO的socket通信demo

  • 2017年10月31日 09:09
  • 8KB
  • 下载

java nio.pdf

  • 2017年10月26日 17:20
  • 2.16MB
  • 下载

实战演示JVM内存四大类型问题:Heap、Stack、Contant、DirectMemory等

如图演示
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:从DirectMemory谈谈Java NIO
举报原因:
原因补充:

(最多只允许输入30个字)