当前位置: 首页 > news >正文

JVM内存结构笔记05-直接内存

文章目录

  • 定义
  • 直接内存(Direct Memory)和堆外内存(Off-Heap Memory)
  • 直接内存的特点
    • 1. 不属于 JVM 运行时数据区
    • 2. 高性能
    • 3. 手动管理
  • 直接内存与堆内存的区别
  • 直接内存是否会被gc回收
    • 思考:垃圾回收不会管理直接内存,所以为什么这个垃圾回收会导致直接内存被释放掉
  • 分配和回收原理
    • 查看源码


定义

  • 直接内存是一种特殊的内存缓冲区,并不在 Java 堆或方法区中分配的,而是通过 JNI 的方式在本地内存上分配的。
  • 直接内存并不是虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域,但是这部分内存也被频繁地使用。而且也可能导致 OutOfMemoryError 错误出现。
  • NIO(Non-Blocking I/O,也被称为 New I/O)引入了一种基于通道(Channel)与缓存区(Buffer)的 I/O 方式,它可以直接使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样就能在一些场景中显著提高性能,因为避免了在 Java 堆和 Native 堆之间来回复制数据。
  • 直接内存的分配不会受到 Java 堆的限制,但是,既然是内存就会受到本机总内存大小以及处理器寻址空间的限制。

直接内存(Direct Memory)和堆外内存(Off-Heap Memory)

直接内存:通常指的是通过Java的java.nio.ByteBuffer类及其allocateDirect方法分配的内存。这种内存不是从JVM的堆中分配的,而是直接从操作系统的本地内存中分配的。因此,它不受JVM堆大小的限制,并且可以减少数据从JVM堆到本地内存之间的复制操作,这对于提高I/O密集型应用的性能特别有用。
堆外内存:是指所有在 Java 堆之外的内存,它是一个相对宽泛的概念,不仅仅包括直接内存,还包括了如本地方法栈、方法区等占用的内存,以及通过 JNI(Java Native Interface)调用本地代码分配的内存等。

直接内存的特点

1. 不属于 JVM 运行时数据区

直接内存是 JVM 外部的内存区域,不受 JVM 堆大小的限制(-Xmx 参数不影响直接内存)。
它由操作系统直接管理,JVM 只是通过本地方法调用操作系统的内存分配函数。

2. 高性能

直接内存的读写性能通常高于堆内存,因为它避免了数据在 JVM 堆和本地内存之间的拷贝。
直接内存适用于需要频繁与本地代码(如操作系统或硬件)交互的场景。

3. 手动管理

直接内存的分配和释放需要手动管理,JVM 不会自动回收直接内存。
如果直接内存使用不当,可能会导致内存泄漏或 OutOfMemoryError。

直接内存与堆内存的区别

在这里插入图片描述

直接内存是否会被gc回收

代码示例

public class Demo {
    static int _1Gb = 1024 * 1024 * 1024;

    /*
     * 让System.gc()失效:
     * -XX:+DisableExplicitGC 
     */
    public static void main(String[] args) throws IOException {
        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(_1Gb);
        System.out.println("分配完毕...");
        System.in.read();
        System.out.println("开始释放...");
        byteBuffer = null;
        System.gc(); // 显式的垃圾回收,Full GC
        System.in.read();
    }
}

运行项目
因为使用的是直接内存,所以在任务管理器中查看
在这里插入图片描述
控制台点击回车,运行代码执行gc回收
发现直接内存被释放了
在这里插入图片描述

思考:垃圾回收不会管理直接内存,所以为什么这个垃圾回收会导致直接内存被释放掉

原因:

  1. DirectByteBuffer对象:当你使用ByteBuffer.allocateDirect(int capacity)来分配直接内存时,实际上创建的是一个DirectByteBuffer实例。这个对象不仅包含指向直接内存块的引用,还包含了其他元数据信息。
  2. Cleaner机制:DirectByteBuffer类内部使用了sun.misc.Cleaner机制。这是一个基于引用队列(ReferenceQueue)和虚引用(PhantomReference)的清洁器,用于在DirectByteBuffer对象变得不可达时触发清理动作。当没有强引用指向某个DirectByteBuffer对象,并且该对象进入垃圾回收阶段时,相关的Cleaner会被触发。
  3. 释放直接内存:当Cleaner被触发时,它会执行一个清理操作,这个操作通常包括调用本地方法来释放之前通过JNI分配的直接内存。也就是说,虽然直接内存本身不由JVM的垃圾收集器直接管理,但DirectByteBuffer对象是堆上的对象,当它被垃圾回收时,其关联的直接内存也会被释放。

如上面代码示例中,使用ByteBuffer.allocateDirect(n)在非堆内存中分配了 n 字节的空间,还在堆上创建了一个DirectByteBuffer对象,该对象持有对这块直接内存的引用。
当没有任何强引用指向byteBuffer对象时(例如,将byteBuffer = null),并且经过垃圾回收后,如果DirectByteBuffer对象被认定为可回收,那么它的Cleaner就会被触发,进而释放掉那块直接内存。

分配和回收原理

使用了 Unsafe 对象完成直接内存的分配回收,并且回收需要主动调用 freeMemory 方法。

ByteBuffer 的实现类内部,使用了 Cleaner (虚引用)来监测 ByteBuffer 对象,一旦 ByteBuffer 对象被垃圾回收,那么就会由 ReferenceHandler 线程通过 Cleaner 的 clean 方法调用 freeMemory 来释放直接内存。

使用Unsafe对象验证Unsafe 对象完成直接内存的分配回收

public class Demo1 {
    static int _1Gb = 1024 * 1024 * 1024;

    public static void main(String[] args) throws IOException {
        Unsafe unsafe = getUnsafe();
        // 分配内存
        // base为内存地址
        long base = unsafe.allocateMemory(_1Gb);
        unsafe.setMemory(base, _1Gb, (byte) 0);
        System.in.read();

        // 释放内存
        unsafe.freeMemory(base);
        System.in.read();
    }

    public static Unsafe getUnsafe() {
        try {
            Field f = Unsafe.class.getDeclaredField("theUnsafe");
            f.setAccessible(true);
            Unsafe unsafe = (Unsafe) f.get(null);
            return unsafe;
        } catch (NoSuchFieldException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }
}

运行代码
在这里插入图片描述
控制台点击回车,发现直接内存被释放
在这里插入图片描述

查看源码

public static void main(String[] args) throws IOException {
    ByteBuffer byteBuffer = ByteBuffer.allocateDirect(_1Gb);
}


java.nio.DirectByteBuffer#DirectByteBuffer(int)

DirectByteBuffer(int cap) {                   // package-private

    super(-1, 0, cap, cap);
    boolean pa = VM.isDirectMemoryPageAligned();
    int ps = Bits.pageSize();
    long size = Math.max(1L, (long)cap + (pa ? ps : 0));
    Bits.reserveMemory(size, cap);

    long base = 0;
    try {
        base = unsafe.allocateMemory(size);
    } catch (OutOfMemoryError x) {
        Bits.unreserveMemory(size, cap);
        throw x;
    }
    unsafe.setMemory(base, size, (byte) 0);
    if (pa && (base % ps != 0)) {
        // Round up to page boundary
        address = base + ps - (base & (ps - 1));
    } else {
        address = base;
    }
    //回调任务对象Deallocator
    //进入Deallocator
    cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
    //Cleaner是一个虚引用类型,当this(DirectByteBuffer对象)被垃圾回收时,会触发Cleaner的clean()
    att = null;
}


java.nio.DirectByteBuffer.Deallocator#run

private static class Deallocator implements Runnable{
    ...
    public void run() {
        if (address == 0) {
            // Paranoia
            return;
        }
        //在这里释放直接内存
        unsafe.freeMemory(address);
        address = 0;
        Bits.unreserveMemory(size, capacity);
    }
}

返回上级进入Cleaner.create

Cleaner是一个虚引用类型,当this(DirectByteBuffer对象)被垃圾回收时,会触发Cleaner的clean()
sun.misc.Cleaner#clean

public class Cleaner extends PhantomReference<Object> {
    public void clean() {
        if (remove(this)) {
            try {
                //执行
                this.thunk.run();
            } catch (final Throwable var2) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        if (System.err != null) {
                            (new Error("Cleaner terminated abnormally", var2)).printStackTrace();
                        }
    
                        System.exit(1);
                        return null;
                    }
                });
            }
        }
    }
}

在以上代码示例中,通过配置 -XX:+DisableExplicitGC 让System.gc()失效(因为System.gc()会回收新生代和老年代的内存影响效率),所以会导致直接内存无法被回收(程序结束时才会回收),此时可以通过Unsafe.freeMemory(long) 来控制直接内存的回收。


相关文章:
JVM内存结构笔记01-运行时数据区域
JVM内存结构笔记02-堆
JVM内存结构笔记03-方法区
JVM内存结构笔记04-字符串常量池
JVM内存结构笔记05-直接内存
JVM中常量池和运行时常量池、字符串常量池三者之间的关系

相关文章:

  • 深度学习----激活函数
  • VS Code 配置优化指南
  • 《大语言模型》学习笔记(一)
  • 大数据任务调度:DolphinScheduler、Airflow 实战(调度策略、任务依赖)
  • Swift 手动导入 RxSwift.xcframework 报错
  • python使用venv命令创建虚拟环境(ubuntu22)
  • SpringCloud带你走进微服务的世界
  • 基于Java + Redis + RocketMQ的库存秒杀系统设计与实现
  • Langchain应用-rag优化
  • 微信小程序从右向左无限滚动组件封装(类似公告)
  • 命令设计模式
  • 【计算机网络】第八版和第七版的主要区别,附PDF
  • 【python】不规则字符串模糊匹配(fuzzywuzzy)
  • AI自动化、资本短视、三输与破局
  • pytest基础知识
  • golang的Map
  • RCE-Labs超详细WP-Level13Level14(PHP下的0/1构造RCE命令简单的字数限制RCE)
  • RabbitMQ 的工作模式
  • 设计模式之适配器模式:原理、实现与应用
  • ConcurrentModificationException:检测到并发修改完美解决方法
  • 做商城网站在哪里注册营业执照/如何优化搜索引擎
  • 长春网长春网站建设站建设/上海关键词优化报价
  • 西安北郊网站建设/女生学网络营销这个专业好吗
  • 泉州做网站设计公司/灰色行业seo
  • 影视公司网站模板/广告宣传费用一般多少
  • 嘉兴云推广网站/google广告投放技巧