Java 堆外内存

文章目录

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

Java 堆外内存

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

Java 堆外内存_第1张图片

如上图所示,堆外内存就是 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:表示字符串表、常量池等所占用的内存。

你可能感兴趣的:(Java,java,jvm,开发语言)