【基础篇】十、JVM堆 && 直接内存

本文探讨了Java堆内存的工作原理,包括堆的使用、堆溢出的示例、内存使用指标以及如何设置堆内存大小。同时介绍了直接内存的作用和管理,以及如何避免直接内存溢出。
摘要由CSDN通过智能技术生成

运行时数据区域,还有两组成部分:堆和方法区,和栈、程序计数器不同,它们是线程共享的

在这里插入图片描述

一、堆

1、堆heap

  • 堆内存是线程共享的
  • 创建出来的对象存于堆内存

如图:栈里的局部变量中存了堆上对象的引用:

在这里插入图片描述

2、堆溢出

ArrayList<Object> list = new ArrayList<>();
while (true) {
    list.add(new byte[1024 * 1024]);
}

一直new对象往堆里放,最终达到堆内存上限值后堆内存溢出OutOfMemory:Java heap space

在这里插入图片描述

3、used、total、max

  • used:已使用的堆内存
  • total:可用堆内存大小
  • max:最大可分配的堆内存
    在这里插入图片描述

调整下上面的代码:

ArrayList<Object> list = new ArrayList<>();
while (true) {
    System.in.read();
    System.out.println("往堆中放入一次...");
    list.add(new byte[1024 * 1024 * 10]);
}

使用阿尔萨斯工具来看JVM信息:

dashboard -i 刷新频率(毫秒)

或者直接使用memory只查看内存:

memory

随着堆中对象变多,used即将达到total时,total值变大,但最大只能到与max相等:

在这里插入图片描述
在这里插入图片描述

并不是当used = max = total的时候,堆内存就溢出了!! 这和垃圾回收有关。

4、设置堆内存大小

添加虚拟机参数:

-Xmx-Xms//eg:
-Xmx1g -Xms1g

在Dockerfile中可写:

ENV JAVA_OPTS="-Xms512m -Xmx512m "

其中:

  • –Xmx是max最大值,-Xms 是初始的total
  • 单位默认字节byte且是1024的倍数,可k或者K(KB)、m或者M(MB)、g或者G(GB)
  • 限制Xmx必须大于 2 MB,Xms必须大于1MB

在这里插入图片描述
开发中,把-Xmx和-Xms设为相同的值,如此,程序启动后的可用内存就是最大内存,无需向JVM频繁申请,以减少申请内存的时间开销。

二、直接内存

直接内存不属于Java运行时的内存区域,用途:

  • Java堆中的对象如果不再使用要回收,回收时会影响对象的创建和使用

  • 普通IO,是文件先读到内存(缓存区),再复制到JVM堆中。引入直接内存则不用复制这一步,直接让堆中存一个引用
    在这里插入图片描述

  • JDK8及以后,存方法区的数据

使用ByteBuffer创建直接内存上的数据:

public class Demo2 {

    public static int size = 1024 * 1024 * 100;
    public static List<ByteBuffer> list = new ArrayList<>();
    public static int count = 0;

    public static void main(String[] args) throws Exception {
        System.in.read();
        while(true){
            ByteBuffer directSpace = ByteBuffer.allocateDirect(size);
            list.add(directSpace);
            System.out.println(++count);
            Thread.sleep(5000);
        }
    }
}

阿尔萨斯查看:

在这里插入图片描述

修改直接内存的上限:

-XX:MaxDirectMemorySize=大小

//eg:-XX:MaxDirectMemorySize=300m

出现直接内存溢出:

在这里插入图片描述

1、 NIO与常规IO

在这里插入图片描述
分别使用ByteBuffer操作直接内存、使用JVM内存进行文件拷贝,耗时分别为63ms、256ms

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

/**
 * 演示 ByteBuffer 作用
 */
public class DirectMemoryDemo {
    static final String FROM = "E:\\bak1\\01-java学习.mp4";
    static final String TO = "E:\\bak2\\abc.mp4";
    static final int _1Mb = 1024 * 1024;

    public static void main(String[] args) {
        io();  //耗时256.8563
        directBuffer();//耗时63.2449
    }
	
	//使用直接内存
    private static void directBuffer() {
        long start = System.nanoTime();
        try (FileChannel from = new FileInputStream(FROM).getChannel();
             FileChannel to = new FileOutputStream(TO).getChannel();
        ) {
            ByteBuffer bb = ByteBuffer.allocateDirect(_1Mb);
            while (true) {
                int len = from.read(bb);
                if (len == -1) {
                    break;
                }
                bb.flip();
                to.write(bb);
                bb.clear();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        long end = System.nanoTime();
        System.out.println("directBuffer 用时:" + (end - start) / 1000_000.0);
    }

    private static void io() {
        long start = System.nanoTime();
        try (FileInputStream from = new FileInputStream(FROM);
             FileOutputStream to = new FileOutputStream(TO);
        ) {
            byte[] buf = new byte[_1Mb];
            while (true) {
                int len = from.read(buf);
                if (len == -1) {
                    break;
                }
                to.write(buf, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        long end = System.nanoTime();
        System.out.println("io 用时:" + (end - start) / 1000_000.0);
    }
}

常规IO和NIO的性能差别大,原因如下,

常规IO的数据拷贝:

Java本身不具备磁盘读写的能力,它要调用磁盘读写的话,必须通过操作系统提供的函数,在JDK中的体现就是native本地方法。

因此涉及CPU用户态和内核态的切换,操作系统将文件先读一部分到系统缓存区(如上面设置的1MB),Java在JVM内存的堆中也开辟一块内存,即上面的byte[]做为缓冲区,通过native方法将文件从系统缓冲区复制到Java的缓冲区(堆的byte[]对象)。

在这里插入图片描述

NIO数据拷贝流程:

和上面的常规IO不同,这次开辟出一块直接内存,这块直接内存系统和Java代码都可以直接访问,少了一次缓冲区的复制操作

在这里插入图片描述
总结:

直接内存主要通过 java.nio 包下的 ByteBuffer 类来进行分配和使用

ByteBuffer.allocateDirect(int capacity)

直接内存可以减少数据在 Java 堆和操作系统内存之间的拷贝次数,从而提高 I/O 的效率,常用于文件读写,虽然直接内存不在对上,但在 ByteBuffer 对象被回收时,直接内存也会被释放。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

-代号9527

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值