文章目录
- 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:表示字符串表、常量池等所占用的内存。