jvm-直接内存中的那点事【详细】
直接内存概述
-
直接内存并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域。但是这部分内存也被频繁使用,而且也可能导致OutOfMemoryError一场出现, 在jdk1.4中新加入了NIO(New Input/Output)类,引入了一种基于信道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用native函数库直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer 对象作为这块内存的引用进行操作,这样能在一些场景中显著提高性能,因为避免了在Java堆和native堆中来回复制数据 显然本机直接内存的分配不会受到Java堆大小的限制,但是,既然是内存,肯定还是会受到本机总内存(包括RAM以及SWAP区或者分页文件)大小及处理器寻址空间的限制,服务器管理员在配置虚拟机参数是,会根据实际内存设置-Xmx等参数信息,但经常忽略直接内存,使得各个内存区域总和大于物理内存限制(包括物理的和操作系统级的限制),从而导致动态扩展出现OutOfMemoryError异常
直接 内存GC
jvm中的直接内存,存在堆内存中其实就是DirectByteBuffer类,它本身很小,真的内存是在堆的外卖呢,这里是映射关系。
每次申请直接内存,都先看看是否已经达到了限定最大的直接内存大小(可以用 -XX:MaxDirectMemorySize设定),如果超出了,就会执行System.gc(),在GC的时候触发Stop-The-World ,因为是直接内存回收,时间会比较长,如果没有回收成功直接内存,并且还是超过直接内存的限额,就会抛出OOM
DirectByteBuffer熬过了几次young gc之后,会进入老年代。当老年代满了之后,会触发Full GC。
因为本身很小,很难占满老年代,因此基本不会触发Full GC,带来的后果是大量堆外内存一直占着不放,无法进行内存回收。还有最后一个办法,就是依靠申请额度超限时触发的system.gc(),但是它会中断进程100ms,如果在这100ms的之间,系统未完成GC,仍会抛出OOM。
直接内存和非直接内存差异
直接 非直接内存的概念与NIO有非常大的关联;
在NIO之前,java.io 的方式是: 磁盘IO --> 直接内存[系统内核态] --> 非直接(堆)内存[用户态] --> 直接内存[系统内核态] --> 磁盘IO 而NIO中,对文件的读写不再跟堆内存关联 磁盘IO --> 系统直接内存 --> 磁盘IO
读写文件时可以直接申请堆外内存。