> 文章列表 > Java 堆外内存

Java 堆外内存

Java 堆外内存

文章目录

  • Java 堆外内存
    • 堆外内存的分配方式
      • 使用 Unsafe 类进行分配
      • 使用 ByteBuffer 进行分配
    • 堆外内存的查看方式

Java 堆外内存

在 Java 虚拟机中,分配对象基本上都是在堆上进行的,然而在有些情况下,缓存的数据量非常大时,使用磁盘或者分布式缓存就会比较合适,这时堆外缓存就是一个比较合适的选择。一般会认为 Java 进程启动后,除了分配的堆(heap)内存之外的内存都为堆外内存。堆外内存在没有引用时,也会被 Java 垃圾收集器进行回收。

在这里插入图片描述

如上图所示,堆外内存就是 Heap Out Memory 部分,在 Java 虚拟机中由 DirectByteBuffer 对象表示。

堆外内存的分配方式

Java 分配堆外内存的方式有两种方式,一种使用Unsafe类来进行分配,另一种使用ByteBuffer来进行分配。

使用 Unsafe 类进行分配

需要注意的是,使用 Unsafe 来进行分配,不受 -XX:MaxDirectMemorySize 参数的限制。

private static final long SIZE = 40 * 1024 * 1024;/* 获取 Unsafe 实例,通过反射的方式来获取,避免触发 SecurityException 通过反射的方式,堆外内存不受 -XX:MaxDirectMemorySize 参数限制*/
private static Unsafe getUnsafeInstance() {try {Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");Field field = unsafeClass.getDeclaredField("theUnsafe");field.setAccessible(true);Unsafe unsafe = (Unsafe) field.get(null);return unsafe;} catch (Exception e) {throw new RuntimeException(e);}
}/* 分配堆外内存方式 1*/
private static void allocateOffHeapMemory1() {Unsafe unsafe = getUnsafeInstance();// 分配内存long address = unsafe.allocateMemory(SIZE);// 重新分配内存,把数据从// unsafe.reallocateMemory(address, SIZE);// 释放内存// unsafe.freeMemory(address);
}

使用 ByteBuffer 进行分配

private static final long SIZE = 40 * 1024 * 1024;/* 分配堆外内存方式 2 受 -XX:MaxDirectMemorySize 参数限制*/
private static void allocateOffHeapMemory2() {ByteBuffer buffer = ByteBuffer.allocateDirect((int) SIZE);// ...
}

堆外内存的查看方式

想要查看一个 Java 进程的堆外内存的信息,可以通过如下命令来启动该 Java 进程。

java -XX:+DisableExplicitGC -XX:MaxDirectMemorySize=40M -XX:NativeMemoryTracking=summary -Xms300M -Xmx300M -classpath [your class]
  • -XX:+DisableExplicitGC:用来显示禁止垃圾收集器回收。
  • -XX:MaxDirectMemorySize=40M:设置直接内存大小为 40M,如果使用 ByteBuffer 来执行分配时,超过 40M 会抛出 OOM 异常。
  • -XX:NativeMemoryTracking=summary:配置查看堆外内存的跟踪。
  • -Xms300M -Xmx300M:将堆内存限定在 300M。

当你的 Java 进程运行起来之后,可以通过下面的命令来查看虚拟机中内存的实际分配。

其中 reserved 表示应用可以使用的内存大小,committed 表示正在使用的内存大小。

# 通过 jps 找出对应进程的 pid
jps# 使用 jcmd 查看汇总信息
jcmd [your process pid] VM.native_memory summary scale=MB# 输出
Native Memory Tracking:Total: reserved=1746MB, committed=478MB
-                 Java Heap (reserved=300MB, committed=300MB)(mmap: reserved=300MB, committed=300MB) -                     Class (reserved=1046MB, committed=19MB)(classes #470)(malloc=14MB #179) (mmap: reserved=1032MB, committed=5MB) -                    Thread (reserved=33MB, committed=33MB)(thread #34)(stack: reserved=33MB, committed=33MB)-                      Code (reserved=244MB, committed=3MB)(mmap: reserved=244MB, committed=3MB) -                        GC (reserved=27MB, committed=27MB)(malloc=16MB #144) (mmap: reserved=11MB, committed=11MB) -                  Internal (reserved=54MB, committed=54MB)(malloc=54MB #1678) -                    Symbol (reserved=1MB, committed=1MB)(malloc=1MB #98) (arena=1MB #1)
  • Java Heep:可以观察到堆(heap)内内存限定在 300M。
  • Class:表示已经加载 class 数量以及占用的内存。
  • Thread:表示目前有多少个线程以及占用的内存。
  • Code:表示编译器生成代码所占用的内存。
  • GC:表示垃圾回收器需要使用多少内存来完成垃圾回收动作。
  • Internal: 包含命令行解析器使用的内存、JVMTI、PerfData 以及 Unsafe 分配的内存等等,可以观察到上述代码分配的 40M 堆外内存包含此处的 54M 内。
    • JVMTI(JVM Tool Interface):是开发和监视 Java 虚拟机的编程接口。
    • PerfData:是 Java 虚拟机中用来记录一些指标数据的文件。
  • Symbol:表示字符串表、常量池等所占用的内存。