笔记 笔记
首页
  • 开发工具
  • Java Web
  • Java 进阶
  • 容器化技术
  • Java 专栏

    • Java 核心技术面试精讲
    • Java 业务开发常见错误 100 例
  • 数据库专栏

    • MySQL 实战 45 讲
    • Redis 核心技术与实战
  • 安全专栏

    • OAuth 2.0 实战课
  • 计算机系统
  • 程序设计语言
  • 数据结构
  • 知识产权
  • 数据库
  • 面向对象
  • UML
  • 设计模式
  • 操作系统
  • 结构化开发
  • 软件工程
  • 计算机网络
  • 上午题错题
在线工具 (opens new window)

EasT-Duan

Java 开发
首页
  • 开发工具
  • Java Web
  • Java 进阶
  • 容器化技术
  • Java 专栏

    • Java 核心技术面试精讲
    • Java 业务开发常见错误 100 例
  • 数据库专栏

    • MySQL 实战 45 讲
    • Redis 核心技术与实战
  • 安全专栏

    • OAuth 2.0 实战课
  • 计算机系统
  • 程序设计语言
  • 数据结构
  • 知识产权
  • 数据库
  • 面向对象
  • UML
  • 设计模式
  • 操作系统
  • 结构化开发
  • 软件工程
  • 计算机网络
  • 上午题错题
在线工具 (opens new window)

购买兑换码请添加

添加时候请写好备注,否则无法通过。

  • 设计模式

  • JVM 详解

    • JVM 与 Java 体系结构
    • 类加载子系统
    • 运行时数据区
    • 程序计数器
    • 虚拟机栈
    • 本地方法接口
    • 本地方法栈
    • 堆
    • 方法区
    • 对象的实例化内存布局与访问定位
    • 直接内存
      • 直接内存概述
      • 直接内存的简单体验
      • 使用本地内存读写数据的测试
      • 直接内存的 OOM 与内存大小的设置
        • 实例一
        • 实例二
      • 总结
    • 执行引擎
    • StringTable
    • 垃圾回收概述
    • 垃圾回收算法
    • 垃圾回收概念
    • 垃圾回收器
  • Linux

  • Redis

  • 分布式锁

  • Shiro

  • Gradle

  • Java 进阶
  • JVM 详解
EasT-Duan
2024-02-18
目录

直接内存

欢迎来到我的 ChatGPT 中转站,极具性价比,为付费不方便的朋友提供便利,有需求的可以添加左侧 QQ 二维码,另外,邀请新用户能获取余额哦!最后说一句,那啥:请自觉遵守《生成式人工智能服务管理暂行办法》。

# 直接内存概述

  • 不是虚拟机运行时数据区的一部分,也不是《Java 虚拟机规范》中定义的内存区域。

  • 直接内存是在 Java 堆外的、直接向系统申请的内存区间。

  • 来源于 NIO,通过存在堆中的 DirectByteBuffer 操作 Native 内存。

  • 通常,访问直接内存的速度会优于 Java 堆。即读写性能高。

    • 因此出于性能考虑,读写频繁的场合可能会考虑使用直接内存。
    • Java 的 NIO 库允许 Java 程序使用直接内存,用于数据缓冲区。

# 直接内存的简单体验

/**
 *  IO                  NIO (New IO / Non-Blocking IO)
 *  byte[] / char[]     Buffer
 *  Stream              Channel
 *
 * 查看直接内存的占用与释放
 */
public class BufferTest {
    private static final int BUFFER = 1024 * 1024 * 1024;//1GB

    public static void main(String[] args) {
        //直接分配本地内存空间
        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(BUFFER);
        System.out.println("直接内存分配完毕,请求指示!");

        Scanner scanner = new Scanner(System.in);
        scanner.next();

        System.out.println("直接内存开始释放!");
        byteBuffer = null;
        System.gc();
        scanner.next();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 使用本地内存读写数据的测试

public class BufferTest1 {

    private static final String TO = "D:\\软件设计师.zip";
    private static final int _100Mb = 1024 * 1024 * 100;

    public static void main(String[] args) {
        long sum = 0;
        String src = "D:\\软件设计师.zip";//1.8G
        for (int i = 0; i < 3; i++) {
            String dest = "D:\\软件设计师_" + i + ".zip";
            // 传统方式
//            sum += io(src,dest);//22672
            // nio 方式
            sum += directBuffer(src, dest);//10155
        }

        System.out.println("总花费的时间为:" + sum);
    }

    private static long directBuffer(String src, String dest) {
        long start = System.currentTimeMillis();

        FileChannel inChannel = null;
        FileChannel outChannel = null;
        try {
            inChannel = new FileInputStream(src).getChannel();
            outChannel = new FileOutputStream(dest).getChannel();

            ByteBuffer byteBuffer = ByteBuffer.allocateDirect(_100Mb);
            while (inChannel.read(byteBuffer) != -1) {
                byteBuffer.flip();//修改为读数据模式
                outChannel.write(byteBuffer);
                byteBuffer.clear();//清空
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (inChannel != null) {
                try {
                    inChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
            if (outChannel != null) {
                try {
                    outChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        }

        long end = System.currentTimeMillis();
        return end - start;

    }

    private static long io(String src, String dest) {
        long start = System.currentTimeMillis();

        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            fis = new FileInputStream(src);
            fos = new FileOutputStream(dest);
            byte[] buffer = new byte[_100Mb];
            while (true) {
                int len = fis.read(buffer);
                if (len == -1) {
                    break;
                }
                fos.write(buffer, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        }


        long end = System.currentTimeMillis();

        return end - start;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103

# 直接内存的 OOM 与内存大小的设置

  • 也可能导致 OutOfMemoryError 异常。
  • 由于直接内存在 Java 堆外,因此它的大小不会直接受限于 - Xmx 指定的最大堆大小,但是系统内存是有限的,Java 堆和直接内存的总和依然受限于操作系统能给出的最大内存。
  • 缺点
    • 分配回收成本较高。
    • 不受 JVM 内存回收管理。
  • 直接内存大小可以通过 MaxDirectMemorySize 设置。
  • 如果不指定,默认与堆的最大值 - Xmx 参数值一致。

# 实例一

/**
 * 本地内存的 OOM:  OutOfMemoryError: Direct buffer memory
 */
public class BufferTest2 {
    private static final int BUFFER = 1024 * 1024 * 20;//20MB

    public static void main(String[] args) {
        ArrayList<ByteBuffer> list = new ArrayList<>();

        int count = 0;
        try {
            while (true) {
                ByteBuffer byteBuffer = ByteBuffer.allocateDirect(BUFFER);
                list.add(byteBuffer);
                count++;
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        } finally {
            System.out.println(count);
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# 实例二

/**
 * -Xmx20m -XX:MaxDirectMemorySize=10m
 */
public class MaxDirectMemorySizeTest {
    private static final long _1MB = 1024 * 1024;
    public static void main(String[] args) throws IllegalAccessException {
        Field unsafeField = Unsafe.class.getDeclaredFields()[0];
        unsafeField.setAccessible(true);
        Unsafe unsafe = (Unsafe) unsafeField.get(null);
        while (true) {
            unsafe.allocateMemory(_1MB);
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 总结

简单来说:java process memory = java heap + native memory。

注意

ChatGPT:这个公式并不完全正确,因为它忽略了一些 JVM 还需要的内存区域,如元空间(Metaspace)、线程栈(Thread Stack)和代码缓存(Code Cache)等。

一个更准确的公式是:

java process memory = java heap + native memory + metaspace + thread stack + code cache + ...

其中:

  • java heap 是 JVM 用来存储 Java 对象的内存区域,受到 -Xms 和 -Xmx 参数的限制。
  • native memory 是 JVM 用来存储本地数据的内存区域,不受到 Java 垃圾回收器的管理,可以通过 NIO、JNI 或 Unsafe 类来分配和释放。
  • metaspace 是 JVM 用来存储类的元数据的内存区域,不属于堆内存,受到 -XX:MetaspaceSize 和 -XX:MaxMetaspaceSize 参数的限制。
  • thread stack 是 JVM 用来存储每个线程的局部变量和方法调用的内存区域,受到 -Xss 参数的限制。
  • code cache 是 JVM 用来存储 JIT 编译后的机器指令的内存区域,受到 -XX:InitialCodeCacheSize 和 -XX:ReservedCodeCacheSize 参数的限制。
#JVM
上次更新: 2025/04/12, 05:37:39
对象的实例化内存布局与访问定位
执行引擎

← 对象的实例化内存布局与访问定位 执行引擎→

最近更新
01
Reactor 核心
02-24
02
前置条件
10-30
03
计算机网络
09-13
更多文章>
Theme by Vdoing | Copyright © 2019-2025 powered by Vdoing
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式