美文网首页程序员互联网科技Java 杂谈
作为 Java 开发者,你需要了解的堆外内存知识

作为 Java 开发者,你需要了解的堆外内存知识

作者: cb9841112785 | 来源:发表于2018-06-15 14:43 被阅读26次

1. 引言

很久没有遇到堆外内存相关的问题了,五一假期刚结束,便不期而遇,以前也处理过几次这类问题,但都没有总结,觉得是时候总结一下了。

先来看一个 Demo:在 Demo 中分配堆外内存用的是 allocateDirect 方法,但其内部调用的是 DirectByteBuffer,换言之,DirectByteBuffer 才是实际操作堆外内存的类,因此,本场 Chat 将围绕 DirectByteBuffer 展开。

import java.nio.ByteBuffer;publicclassDemo{publicstaticvoidmain( String[] args ){//分配一块1024Bytes的堆外内存(直接内存) //allocateDirect方法内部调用的是DirectByteBuffer ByteBuffer buffer=ByteBuffer.allocateDirect(1024);         System.out.println(buffer.capacity());//向堆外内存中读写数据 buffer.putInt(0,2018);         System.out.println(buffer.getInt(0));            } }

2. 什么是堆外内存?

Java 开发者一般都知道堆内存,但却未必了解堆外内存。事实上,除了堆内存,Java 还可以使用堆外内存,也称直接内存(Direct Memory)。

顾名思义,堆外内存是在 JVM Heap 之外分配的内存块,并不是 JVM 规范中定义的内存区域,堆外内存用得并不多,但十分重要。

读者也许会有一个疑问:既然已经有堆内存,为什么还要用堆外内存呢?这主要是因为堆外内存在 IO 操作方面的优势。

举一个例子:在通信中,将存在于堆内存中的数据 flush 到远程时,需要首先将堆内存中的数据拷贝到堆外内存中,然后再写入 Socket 中;

如果直接将数据存到堆外内存中就可以避免上述拷贝操作,提升性能。类似的例子还有读写文件。

目前,很多 NIO 框架 (如 netty,rpc) 会采用 Java 的 DirectByteBuffer 类来操作堆外内存,DirectByteBuffer 类对象本身位于 Java 内存模型的堆中,由 JVM 直接管控、操纵。

但是,DirectByteBuffer 中用于分配堆外内存的方法 unsafe.allocateMemory(size) 是个一个 native 方法,本质上是用 C 的 malloc 来进行分配的。

分配的内存是系统本地的内存,并不在 Java 的内存中,也不属于 JVM 管控范围,所以在 DirectByteBuffer 一定会存在某种特别的方式来操纵堆外内存。

3. 堆外内存创建过程深度解析

首先,我们来看一下 DirectByteBuffer 源代码,从中洞悉分配堆外内存的过程:

3.1 第一个重要方法:

Bits.reserveMemory(size, cap);

源代码如下:

该方法用于在系统中保存总分配内存(按页分配)的大小和实际内存的大小,具体执行中需要首先用 tryReserveMemory 方法来判断系统内存(堆外内存)是否足够,具体代码如下:

从 Bits.reserveMemory(size, cap) 源码可以看出,其执行过程中,可能遇到以下三种情况:

1. 最乐观的情况:可用堆外内存足够,reserveMemory 方法返回 true,该方法结束。

2. 如果不幸,堆外内存不足,则须进行第二步:

jlra.tryHandlePendingReference()

会触发一次非堵塞的

Reference#tryHandlePending(false),该方法会将已经被 JVM 垃圾回收的 DirectBuffer 对象的堆外内存释放。

3. 如果在进行一次堆外内存资源回收后,还不够进行本次堆外内存分配的话,则进行 GC 操作:

System.gc() 会触发一个 Full GC,当然,前提是你没有显示的设置 - XX:+DisableExplicitGC 来禁用显式 GC。同时,需要注意的是,调用 System.gc() 并不能够保证 Full GC 马上就能被执行。

调用 System.gc() 后,接下来会最多进行 9 次循环尝试,仍然通过 tryReserveMemory 方法来判断是否有足够的堆外内存可供分配操作。每次尝试都会 sleep,以便 Full GC 能够完成,如下代码所示。

4. 最不幸的情况,经过 9 次循环尝试后,如果仍然没有足够的堆外内存,将抛出 OutOfMemoryError 异常。

综上所述,Bits.reserveMemory(size, cap) 方法将依次执行以下操作:

1.如果可用堆外内存足以分配给当前要创建的堆外内存大小时,直接返回 True;

2.如果堆外内存不足,则触发一次非堵塞的 Reference#tryHandlePending(false)。该方法会将已经被 JVM 垃圾回收的 DirectBuffer 对象的堆外内存释放;

3.如果进行一次堆外内存资源回收后,还不够进行本次堆外内存分配的话,则进行 System.gc()。

System.gc() 会触发一个 Full GC,需要注意的是,调用 System.gc() 并不能够保证 Full GC 马上就能被执行。

所以在后面打代码中,会进行最多 9 次尝试,看是否有足够的可用堆外内存来分配堆外内存。

并且每次尝试之前,都对延迟等待时间,已给 JVM 足够的时间去完成 Full GC 操作。

4.如果 9 次尝试后依旧没有足够的可用堆外内存来分配本次堆外内存,则抛出 OutOfMemoryError(“Direct buffer memory”) 异常。

3.2 第二个重要方法:

unsafe.allocateMemory(size)

......

3.3 第三个重要方法:

Cleaner.create(this, new Deallocator(base, size, cap))

......

最后给大家分享个Java交流学习群:779792048,群内提供免费架构视频资料,还有大牛在群里解答!

相关文章

  • 作为 Java 开发者,你需要了解的堆外内存知识

    1. 引言 很久没有遇到堆外内存相关的问题了,五一假期刚结束,便不期而遇,以前也处理过几次这类问题,但都没有总结,...

  • 堆外内存 之 DirectByteBuffer 详解

    堆外内存 堆外内存是相对于堆内内存的一个概念。堆内内存是由JVM所管控的Java进程内存,我们平时在Java中创建...

  • 堆外内存 之 DirectByteBuffer 详解

    堆外内存 堆外内存是相对于堆内内存的一个概念。堆内内存是由JVM所管控的Java进程内存,我们平时在Java中创建...

  • 直接内存与 JVM 源码分析

    直接内存(堆外内存) 直接内存有一种叫法,堆外内存。 直接内存(堆外内存)指的是 Java 应用程序通过直接方式从...

  • Netty-内存管理

    但是内存拷贝对性能有可能影响比较大,所以Java中可以绕开堆内存直接操作堆外内存,问题是创建堆外内存的速度比堆内存...

  • Java 垃圾回收机制

    垃圾回收机制让开发者不需要考虑内存管理,这样不仅提高了开发效率,还改善了内存的使用状况。 Java堆内存 堆在JV...

  • 堆外内存 Unsafe DirectByteBuffer

    堆内堆外 我们在Java中创建的对象都处于堆内内存(heap)中,堆内内存是由JVM所管控的Java进程内存,并且...

  • Java(L1)--面向对象

    Java知识点梳理 Chapter 3 面向对象的基本概念 堆内存与栈内存堆内存:存放对象的属性内容,需要new关...

  • Netty系列之Direct Buffers

    1、什么是堆外内存堆外内存是相对于堆内内存的一个概念。堆内内存是由JVM所管控的Java进程内存,我们平时在Jav...

  • 堆外内存和虚引用

    堆外内存 概念 堆内内存在 Java 中对象都是在堆内分配的,通常我们说的JVM 内存也就指的堆内内存,堆内内存完...

网友评论

    本文标题:作为 Java 开发者,你需要了解的堆外内存知识

    本文链接:https://www.haomeiwen.com/subject/ophmeftx.html