从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,支持阻塞和非阻塞两种模式。

JVM的DirectMemory设置

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

DirectMemory(堆外(Off-Heap)缓存BigMemory的一个实现)

就Java 本地Cache(非分布式)而言,存在3个地方可以存放数据:Heap, OffHeap和Disk BigMemory非开源产品,所以其源码不能通过正常渠道拿到。同时...
  • philos3
  • philos3
  • 2016年04月09日 09:48
  • 1924

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

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

Netty报Direct buffer memory错误

最近在基于Netty实现NIO方面做应用底层通讯架构(服务发现,分布式,高可用,软负载)。遇到java.lang.OutOfMemoryError: Direct buffer memory内存溢出错...
  • u010906369
  • u010906369
  • 2016年12月02日 10:54
  • 2178

Direct Memory

1、不属于java堆内存、分配内存其实是调用操作系统的Os:malloc()函数。 2、容量可通过-XX:MaxDirectMemorySize指定,如果不指定,则默认与Java堆的最大值(-Xmx指...
  • youling_lh
  • youling_lh
  • 2013年08月12日 22:42
  • 1063

Apache DirectMemory介绍

Apache DirectMemory 是一个多层的缓存系统,特性包括无堆的内存管理用于支持大规模的 Java 对象,而不会影响 JVM 垃圾收集器的性能。...
  • tianwei7518
  • tianwei7518
  • 2015年05月28日 15:34
  • 1194

java NIO 直接与非直接缓冲区

ByteBuffer有两个创建缓冲区的方法: static ByteBuffer allocate(int capacity) static ByteBuffer allocate...
  • cloudeagle_bupt
  • cloudeagle_bupt
  • 2016年08月18日 01:22
  • 1536

Java_JVM参数-XX:MaxDirectMemorySize 与 两种 ByteBuffer: heap,direct ByteBuffer

ByteBuffer有两种: heap ByteBuffer -> -XX:Xmx 1.一种是heap ByteBuffer,该类对象分配在JVM的堆内存里面,直接由Java虚拟机负责垃圾回收,...
  • u010003835
  • u010003835
  • 2016年10月28日 17:07
  • 7347

Java Max Direct Memory Size设置

注意Max Direct Memory Szie的默认设置。
  • cloud_ll
  • cloud_ll
  • 2016年03月06日 21:47
  • 5250

《深入理解java虚拟机》学习笔记2——Java内存溢出实例

通过简单的小例子程序,演示java虚拟机各部分内存溢出情况: (1).java堆溢出: Java堆用于存储实例对象,只要不断创建对象,并且保证GC Roots到对象之间有引用的可达,避免垃圾收集器回收...
  • chjttony
  • chjttony
  • 2012年08月12日 17:12
  • 8174
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:从DirectMemory谈谈Java NIO
举报原因:
原因补充:

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