首页 > 系统相关 >JVM虚拟机系统性学习-运行时数据区(方法区、程序计数器、直接内存)

JVM虚拟机系统性学习-运行时数据区(方法区、程序计数器、直接内存)

时间:2023-12-16 15:31:30浏览次数:37  
标签:ByteBuffer int 虚拟机 System 计数器 内存 JVM time 直接

方法区

方法区本质上是 Java 编译后代码的存储区域,存储了每一个类的结构信息,如:运行时常量池、成员变量、方法、构造方法和普通方法的字节码指令等内容

方法区主要存储的数据如下:

  • Class
  1. 类型信息,如该 Class 为 class 类、接口、枚举、注解,类的修饰符等等信息
  2. 方法信息(方法名称、方法返回值、方法参数等等)
  3. 字段信息:保存字段信息,如字段名称、字段类型、字段修饰符
  4. 类变量(静态变量):JDK1.7 之后转移到堆中存储
  • 运行时常量池(字符串常量池):JDK1.7 之后,转移到堆中存储
  • JIT 编译器编译之后的代码缓存

方法区的具体实现有两种:永久代(PermGen)、元空间(Metaspace)

  • JDK1.8 之前通过永久代实现方法区,JDK1.8 及之后使用元空间实现方法区
  • 这两种实现的不同,从存储位置来看:
  • 永久代使用的内存区域为 JVM 进程所使用的区域,大小受 JVM 限制
  • 元空间使用的内存区域为物理内存区域,大小受机器的物理内存限制
  • 从存储内容来看:
  • 永久代存储的信息上边方法区中规定的信息
  • 元空间只存储类的元信息,而静态变量和运行时常量池都转移到堆中进行存储

为什么永久代要被元空间替换?

  • 字符串存在永久代中,容易出现性能问题和永久代内存溢出。
  • 类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。
  • 永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。

常量池

  • class常量池:一个class文件只有一个class常量池
    字面量:数值型(int、float、long、double)、双引号引起来的字符串值等
    符号引用:Class、Method、Field等
  • 运行时常量池:一个class对象有一个运行时常量池
    字面量:数值型(int、float、long、double)、双引号引起来的字符串值等
    符号引用:Class、Method、Field等
  • 字符串常量池:全局只有一个字符串常量池
    双引号引起来的字符串值

程序计数器

程序计数器用于存储当前线程所执行的字节码指令的行号,用于选取下一条需要执行的字节码指令

分支,循环,跳转,异常处理,线程回复等都需要依赖这个计数器来完成

通过程序计数器,可以在线程发生切换时,可以保存该线程执行的位置

直接内存

直接内存(也成为堆外内存)并不是虚拟机运行时数据区的一部分,直接内存的大小受限于系统的内存

在 JDK1.4 引入了 NIO 类,在 NIO 中可以通过使用 native 函数库直接分配堆外内存,然后通过存储在堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作

使用直接内存,可以避免了 Java 堆和 Native 堆中来回复制数据

直接内存使用场景:

  • 有很大的数据需要存储,且数据生命周期长
  • 频繁的 IO 操作,如网络并发场景

直接内存与堆内存比较:

  • 直接内存申请空间耗费更高的性能,当频繁申请到一定量时尤为明显
  • 直接内存IO读写的性能要优于普通的堆内存,在多次读写操作的情况下差异明显

直接内存相比于堆内存,避免了数据的二次拷贝。

  • 我们先来分析不使用直接内存的情况,我们在网络发送数据需要将数据先写入 Socket 的缓冲区内,那么如果数据存储在 JVM 的堆内存中的话,会先将堆内存中的数据复制一份到直接内存中,再将直接内存中的数据写入到 Socket 缓冲区中,之后进行数据的发送
  • 为什么不能直接将 JVM 堆内存中的数据写入 Socket 缓冲区中呢?
    在 JVM 堆内存中有 GC 机制,GC 后可能会导致堆内存中数据位置发生变化,那么如果直接将 JVM 堆内存中的数据写入 Socket 缓冲区中,如果写入过程中发生 GC,导致我们需要写入的数据位置发生变化,就会将错误的数据写入 Socket 缓冲区
  • 那么如果使用直接内存的时候,我们将数据直接存放在直接内存中,在堆内存中只存放了对直接内存中数据的引用,这样在发送数据时,直接将数据从直接内存取出,放入 Socket 缓冲区中即可,减少了一次堆内存到直接内存的拷贝

JVM虚拟机系统性学习-运行时数据区(方法区、程序计数器、直接内存)_System

直接内存与非直接内存性能比较:

public class ByteBufferCompare {
    public static void main(String[] args) {
        //allocateCompare(); //分配比较
        operateCompare(); //读写比较
    }

    /**
     * 直接内存 和 堆内存的 分配空间比较
     * 结论: 在数据量提升时,直接内存相比非直接内的申请,有很严重的性能问题
     */
    public static void allocateCompare() {
        int time = 1000 * 10000; //操作次数,1千万
        long st = System.currentTimeMillis();
        for (int i = 0; i < time; i++) {
            //ByteBuffer.allocate(int capacity) 分配一个新的字节缓冲区。
            ByteBuffer buffer = ByteBuffer.allocate(2); //非直接内存分配申请
        }
        long et = System.currentTimeMillis();
        System.out.println("在进行" + time + "次分配操作时,堆内存 分配耗时:" +
                (et - st) + "ms");
        long st_heap = System.currentTimeMillis();
        for (int i = 0; i < time; i++) {
            //ByteBuffer.allocateDirect(int capacity) 分配新的直接字节缓冲区。
            ByteBuffer buffer = ByteBuffer.allocateDirect(2); //直接内存分配申请
        }
        long et_direct = System.currentTimeMillis();
        System.out.println("在进行" + time + "次分配操作时,直接内存 分配耗时:" +
                (et_direct - st_heap) + "ms");
    }

    /**
     * 直接内存 和 堆内存的 读写性能比较
     * 结论:直接内存在直接的IO 操作上,在频繁的读写时 会有显著的性能提升
     */
    public static void operateCompare() {
        int time = 10 * 10000 * 10000; //操作次数,10亿
        ByteBuffer buffer = ByteBuffer.allocate(2 * time);
        long st = System.currentTimeMillis();
        for (int i = 0; i < time; i++) {
            // putChar(char value) 用来写入 char 值的相对 put 方法
            buffer.putChar('a');
        }
        buffer.flip();
        for (int i = 0; i < time; i++) {
            buffer.getChar();
        }
        long et = System.currentTimeMillis();
        System.out.println("在进行" + time + "次读写操作时,非直接内存读写耗时:" +
                (et - st) + "ms");
        ByteBuffer buffer_d = ByteBuffer.allocateDirect(2 * time);
        long st_direct = System.currentTimeMillis();
        for (int i = 0; i < time; i++) {
            // putChar(char value) 用来写入 char 值的相对 put 方法
            buffer_d.putChar('a');
        }
        buffer_d.flip();
        for (int i = 0; i < time; i++) {
            buffer_d.getChar();
        }
        long et_direct = System.currentTimeMillis();
        System.out.println("在进行" + time + "次读写操作时,直接内存读写耗时:" +
                (et_direct - st_direct) + "ms");
    }
}

标签:ByteBuffer,int,虚拟机,System,计数器,内存,JVM,time,直接
From: https://blog.51cto.com/u_16186397/8852529

相关文章

  • Python学习之十六_virsh批量获取虚拟机IP地址的方法
    Python学习之十六_virsh批量获取虚拟机IP地址的方法Linux命令说明forjin\$(foriin`virshlist|grep-vId|greprunning|awk'{print$2}'`;\dovirshdumpxml$i|grep"macaddress"|awk-F\''{print$2}'&&e......
  • 程序计数器
    一、概述程序计数器(ProgramCounterRegister)是一块较小的内存空间,它可以看做是当前线程所执行的字节码的行号指示器,在Java虚拟机的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,它是程序控制流的指示器;分支、循环、跳转......
  • JVM虚拟机系统性学习-运行时数据区(虚拟机栈、本地方法栈)
    虚拟机栈虚拟机栈为每个线程所私有的,如下图:栈帧是什么?栈帧存储了方法的局部变量表、操作数栈、动态链接和方法返回地址等信息栈内存为线程私有的空间,每个方法在执行时都会创建一个栈帧,执行该方法时,就会将该方法所对应的栈帧入栈局部变量表:用于存储方法参数和定义在方法体内部的局部......
  • 玩转jvm
    1:什么是JVMJVM是Java虚拟机(JavaVirtualMachine)的缩写。它是Java编程语言的关键组成部分。JVM是一个运行在计算机上的虚拟机,它可以执行Java字节码(Javabytecode)程序。Java字节码是Java源代码经过编译后生成的中间代码,在JVM上可以被解释器实时地执行或者被编译成本地机器码执行......
  • springcloudalibabada搭建过程中springboot启动卡住起不来 (Started MoonceProviderApp
    如下图一样springcloudAlibaba在创建新模块之后启动新模块没有注册到nacos上,而是直接卡住起不来原因 原因是:引入了错误的web包: 解决办法:引入相应的 spring-boot-starter-web包:<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot......
  • VMware Workstation 17中设置虚拟机开机自启动
    VMwareWorkstation17中设置虚拟机开机自启动的步骤如下:打开VMwareWorkstation17。在左侧导航栏点击配置自动启动虚拟机。然后选择要自动启动的虚拟机并配置启动顺序,点击确定。设置自动启动服务。打开任务管理器,点击服务,找到VmwareAutostartService,右键,点击开始。找到VMware......
  • 如何在 macOS Sonoma 虚拟机中安装 VMware Tools
     vmware-tools VMwareTools简介VMwareTools中包含一系列服务和模块,可在VMware产品中实现多种功能,从而使用户能够更好地管理客户机操作系统,以及与客户机操作系统进行无缝交互。VMwareTools具备以下功能:将消息从主机操作系统传递到客户机操作系统。将客户机操作......
  • Java-Jvm中GC类型及执行时机
    Java-Jvm中GC类型及执行时机堆的结构年轻代(YoungGeneration):Eden区:在年轻代中,对象首先被分配到Eden区。大多数对象很快就会被回收,所以它们被分配到Eden区以提高分配速度。Survivor区(S0和S1):Eden区中存活的对象会被移动到Survivor区。每次发生MinorGC时,存活的对象会被移......
  • VirtualBox 配置主机访问虚拟机,及CentOS 7.9安装
    CentOS7.9Everything下载地址:http://mirrors.163.com/centos/7.9.2009/isos/x86_64/CentOS-7-x86_64-Everything-2207-02.iso重启服务器。注意,必须重启才会生效。配置虚拟机和物理机的通信桥接模式:实现虚拟机在真实的网络上;NAT模式:实现虚拟机隐藏在......
  • Centos7虚拟机连不上网络
    使用VM创建并安装Centos7,完成之后发现网络不通(pingjd.com可以试一下)解决方法:1、在创建虚拟机的时候选择的是NAT模式 2、在虚拟机中,编辑->虚拟网络编辑器(记住子网IP、子网掩码、网关) 点击NAT设置 3、在Centos7中输入ipaddr,在这里是ens33(记住mac地址)4、在Centos7中输......