【JVM】Java虚拟机运行时数据分区介绍

JVM 分区(运行时数据区域)

文章目录

  • JVM 分区(运行时数据区域)
    • 前言
    • 1. 程序计数器
    • 2. Java 虚拟机栈
    • 3. 本地方法栈
    • 4. Java 堆
    • 5. 方法区
    • 6. 运行时常量池
    • 7. 直接内存

前言

之前在说多线程的时候,提到了JVM虚拟机的分区内存,如下所示,那次简单鸽了一下没有详细的介绍JVM的各个分区的主要功能/生命周期等内容,在这里补上。
本博客通过图文/代码示例等,详细的介绍了JVM的各个分区的功能,写作不易,求个关注!


参考资料《深入理解Java虚拟机》

在这里插入图片描述

1. 程序计数器

程序计数器是一块较小的内存空间,它是线程所有的,随着线程的出现而出现,随着线程的消亡而消亡,它可以看做是当前线程所执行的字节码的行号指示器

Java虚拟机的多线程是通过线程轮流切换、分配处理器执行时间的方式来实现的,在任何一个确定时刻,一个处理器(对于多核处理器来说是一个内核)都只会执行一条线程中的指令。当线程执行时会被CPU调度,时间分片结束后会被再次挂起,直到再一次被CPU调度的时候,就会再次执行一个分片单位。为了保证线程在恢复调度时候能正确的在原有进度上继续向下执行,每条线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储

**如果正在执行的是本地(Native)方法,这个计数器值值应该为空。**程序计数器是唯一一个没有规定任何OutOfMemoryError情况的区域。

2. Java 虚拟机栈

虚拟机栈也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的线程内存模型:每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧[1](Stack Frame)用于存储局部变量表、操作数栈、动态连接、方法出口等信息。每一个方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

局部变量表存放了编译期可知的各种Java虚拟机基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,它并不等同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或者其他与此对象相关的位置)和returnAddress 类型(指向了一条字节码指令的地址)。

这些数据类型在局部变量表中的存储空间以局部变量槽(Slot)来表示,其中64位长度的long和double类型的数据会占用两个变量槽,其余的数据类型只占用一个。局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在栈帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。

如果线程请求的栈深度大于虚拟机所允许的深度,将会抛出StackOverflowError异常

如果Java虚拟机栈容量可以动态扩展,当栈扩展时无法申请到足够的内存会抛出OutOfMemoryError(OOM)异常

以下面代码为例,讲一下栈与栈帧:

public class TestA {
    public static void main(String[] args) {
        hello();
    }
    
    public static void hello() {
        System.out.println("Hello World!I'm Jim.kk!")
    }
   
}
  1. 以上代码在开始执行的时候,会遇到main方法,此时main方法入栈
  2. 当遇到hello方法的时候,hello方法入栈
  3. hello方法内有一个print方法,print方法入栈
  4. print方法执行完毕,print方法出栈
  5. hello方法执行完毕,hello方法出栈
  6. main方法执行结束,main方法出栈
  7. 线程结束,栈消亡

在这里插入图片描述

3. 本地方法栈

本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别只是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的本地(Native)方法服务。

不过,《Java虚拟机规范》并没有强制规定本地方法栈一定要独立出来,因此有的Java虚拟机(如Hot-Spot虚拟机)直接把本地方法栈和虚拟机栈合二为一。

本地方法栈也会在栈深度溢出或栈扩展失败时分别抛出StackOverflowError和OutOfMemoryError异常。

4. Java 堆

Java堆(Java Heap)是虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,Java 世界里“几乎”所有的对象实例都在这里分配内存。

Java堆(Java Heap)是虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,Java 世界里“几乎”所有的对象实例都在这里分配内存。

Java堆(Java Heap)是虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,Java 世界里“几乎”所有的对象实例都在这里分配内存。

堆既可以被设计成事固定大小的,也可以被设计成事可扩展的,如果是可扩展的话,如果在Java堆中没有内存完成实例分配,并且堆也无法再扩展时,Java虚拟机将会抛出OutOfMemoryError异常。

以下内容存储在堆中:

public class JimTest {
    // 1. 实例变量|存储在堆中
    private int num1;
    
    public static void main(String[] args) {
        // 2. 普通对象|存储在堆中
        List<String> list = new ArrayList<>();
        
        // 3. 数组|存储在堆中
        int[] ints = new int[10];
        
        // 4. 字符串对象|存储在堆中(注意这里是字符串对象)
        String str1 = new String("CSDN/Jim.kk");
        
        // 字符串子面量|不不不不不不不存储在堆中(注意这里是不存在堆中)
        String str2 = "CSDN/Jim.kk";
    }
}

5. 方法区

方法区(线程共享),用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。

在JDK7之前,程序员喜欢称呼方法区为“永久代”,但是永久代并不等价于方法区,只不过HotSpot的设计团队奖收集器的分带设计扩展至方法区,用永久代来实现方法区而已,这样就能够像管理Java堆一样管理这部分内存,省去专门为方法区编写内存管理代码的工作。但这并不是一个好主意,这种设计导致了Java应用更容易遇到内存溢出的问题(永久代有-XX:MaxPermSize的上限,即使不设置也有默认大小,而J9和JRockit只要没有触碰到进程可用内存的上限,例如32位系统中的4GB限制,就不会出问题),而且有极少数方法(例如String::intern())会因永久代的原因而导致不同虚拟机下有不同的表现。

在JDK 6的时候HotSpot开发团队就有放弃永久代,逐步改为采用本地内存(Native Memory)来实现方法区的计划了[1],到了JDK 7的HotSpot,已经把原本放在永久代的字符串常量池、静态变量等移出,而到了JDK 8,终于完全废弃了永久代的概念,改用与JRockit、J9一样在本地内存中实现的元空间(Meta-space)来代替,把JDK 7中永久代还剩余的内容(主要是类型信息)全部移到元空间中。

注意,JDK8开始,不存在永久代,取而代之的是元空间

并非进入该区域的内容就是永久存在了,该区域的内存也会进行回收,回收目标主要是针对常量池的回收和对类型的卸载。

以下是一些存储在方法区中的内容:

public class MethodAreaExample {
    // 静态变量|存储在方法区中
    private static int staticVar = 42;

    // 构造方法|字节码存储在方法区中
    public MethodAreaExample(int value) {
        this.instanceVar = value;
    }

    // 静态方法|字节码存储在方法区中
    public static void staticMethod() {
        System.out.println("This is a static method.");
    }

    // 普通方法|字节码存储在方法区中
    public void instanceMethod() {
        System.out.println("This is an instance method.");
    }
    
}

在开发中我们经常会写一些工具类,里面有非常多的静态方法,我们可以通过类名.方法名()的方式调用这些方法,同时我们也经常提到静态内容是属于类的,因此静态的信息成员属性、成员方法会被存储在方法区中,另外既然都叫方法去了,那么方法的字节码肯定也是存储在里面的。

6. 运行时常量池

运行时常量池(Runtime Constant Pool)是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池表(Constant Pool Table),用于存放编译期生成的各种基本类型的常量、字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。

注意,除了运行时常量池外,还有一个Class常量池,Class常量池是在编译的时候就能确定的,但是运行时常量池是动态的,Java并不要求常量一定只有编译期才会产生,也就是说,并非预置入Class文件中的常量池才能进入方法区运行时常量池,运行期间也可以将新的常量放入池中。比如我们通过反射创建的常量等。

以下是存储在常量池中的举例

public class CompileTimeConstantPoolExample {
    // 编译时常量池中的常量
    private static final int CONSTANT_INT = 42;
    private static final String CONSTANT_STRING = "CSDN/Jim.kk";

    public static void main(String[] args) {
        // 字符串字面量,存储在编译时常量池中
        String str1 = "CSDN/Jim.kk";
        
        // 比较字符串常量
        System.out.println(str1 == "CSDN/Jim.kk"); // true,因为常量池中的字符串是同一对象

        // 字符串对象,存储在堆中
        String str2 = new String("CSDN/Jim.kk");
        
        // 比较堆中的字符串对象和常量池中的字符串常量
        System.out.println(str1 == str2); // false,因为str2是在堆中新创建的对象
    }
}

以下是存储在运行时常量池中的举例

public class RuntimeConstantPoolExample {
    public static void main(String[] args) {
        // 字符串字面量,存储在运行时常量池中
        String str1 = "CSDN/Jim.kk";
        
        // 通过 new 创建的字符串对象,存储在堆中
        String str2 = new String("CSDN/Jim.kk");
        
        // 调用 intern() 方法,将字符串对象添加到运行时常量池中,但是由于常量池中已经存在CSDN/Jim.kk字符串,因此这里其实是与str1共享一个字符串
        String str3 = str2.intern();
        
        // 比较引用
        System.out.println(str1 == str3); // true,因为 str3 是运行时常量池中的引用,与 str1 相同
    }
}

7. 直接内存

直接内存并不是虚拟机内存的一部分,可以理解成是直接使用物理内存条上的地址。

由于在JDK1.4中新加入了NIO,引入了基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。这样的操作能够在一些场景中显著提高性能,因为避免了Java堆和Native堆中来回复制数据。

如果调用的内存超过物理机内存大小,依然会抛出OutOfMemoryError异常,毕竟没有了就没法继续申请了。

这里补充一嘴,在计算机体系结构中,32位和64位通常指的是处理器的数据总线宽度和寻址能力,这直接影响到系统可以支持的最大内存大小,一般来说32位可用的最大内存是232字节,即4GB,而64位的电脑可以使用264的内存,是17,179,869,184 GB,16,384 TB(但是一般都会受到CPU限制,比如现在的电脑虽然都是64位了,但是有的CPU最大仅支持32GB的内存条,有的最大支持64GB)。

对着一部分有疑问的话可以学一下NIO或者Netty。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/752004.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

数值稳定性、模型初始化和激活函数

一、数值稳定性&#xff1a;神经网络很深的时候数据非常容易不稳定 1、神经网络梯度 h^(t-1)是t-1层的输出&#xff0c;也就是t层的输入&#xff0c;y是需要优化的目标函数&#xff0c;向量关于向量的倒数是一个矩阵。 2、问题&#xff1a;梯度爆炸、梯度消失 &#xff08;1&…

OpenAI“断供”对我们的影响之我见

1.新闻 OpenAI决定于7月关闭国内GPT访问 近日&#xff0c;美国人工智能公司OpenAI宣布&#xff0c;将于7月起关闭对中国内地的GPT访问&#xff0c;此举引发了业内广泛关注和讨论。以下是关于此新闻的具体信息&#xff1a; 关闭时间&#xff1a;OpenAI官方推送的邮件指出&…

Leaflet【五】Marker点闪烁效果

控制点的透明度 在创建marker的构造当中会传递一个配置对象&#xff0c;这个里面就可以配置对应的透明度opacity&#xff0c;那么只需要去修改这个透明度的值就好了。通过定时器去一直改值即可。 const changeOpacity (entity) > {let i 1;let int setInterval(() >…

谷歌发布两款新Gemma 2大语言模型;阿里云开源Qwen2-72B模型荣登榜首

&#x1f989; AI新闻 &#x1f680; 谷歌发布两款新Gemma 2大语言模型 摘要&#xff1a;谷歌发布Gemma 2大语言模型&#xff0c;包括90亿和270亿参数两种版本。Gemma 2在推理性能、效率和安全性上较第一代有显著提升。27B模型的性能媲美更大规模的主流模型&#xff0c;且部署…

【C++题解】1721. 输出个位为5或者个位为8数

问题&#xff1a;1721. 输出个位为5或者个位为8数 类型&#xff1a;简单循环 题目描述&#xff1a; 请从小到大输出 1∼n 中所有个位为 5 或者个位为8 的所有的整数&#xff0c;每行 1 个。 比如&#xff0c;假设 n20&#xff0c;那么满足条件的数输出如下&#xff1a; 5 8 1…

尊重·理解·协同:论团队合作中的认知提升与信誉建设

零、背景 为什么写博客&#xff1f; 给自己灌输大道理—唠叨哲学 定期总结&#xff1a;反思这段时间内的生活、学习或工作中的得失&#xff0c;提炼出具有普适性的经验和教训。 紧跟热点新闻来有点流量 独特视角&#xff1a;尽量优先进行——人云亦云&#xff0c;先学某一…

MQTT遗嘱信息(2)

接前一篇文章&#xff1a;MQTT遗嘱信息&#xff08;1&#xff09; 本文内容参考&#xff1a; 什么是MQTT遗嘱消息&#xff1f;如何配置和处理遗嘱消息&#xff1f;_mqtt last will-CSDN博客 MQTT 协议学习&#xff1a;Retained&#xff08;保留消息&#xff09; 与 LWT&#x…

Stream Lua Nginx Module 插件一键安装

文章目录 一、场景说明二、脚本职责三、参数说明四、操作示例五、注意事项 一、场景说明 本自动化脚本旨在为提高研发、测试、运维快速部署应用环境而编写。 脚本遵循拿来即用的原则快速完成 CentOS 系统各应用环境部署工作。 统一研发、测试、生产环境的部署模式、部署结构、…

Linux容器篇-Docker容器的使用

文章目录 前言一、Docker的安装主机环境准备关闭防火墙关闭selinux时间同步关闭 swap配置操作系统yum源配置国内Docker-ce镜像源注意 二、安装docker-ce三、配置镜像加速器阿里云镜像加速器生成 四、Docker的使用Docker 客户端获取镜像启动容器查看所有的容器&#xff1a;启动已…

内外网共享文件最优方案,了解一下

基于安全性、合规性、数据防泄漏等原因&#xff0c;为了保护核心数据&#xff0c;企业一般会做内外网隔离&#xff0c;隔离后仍存在数据交换共享的需求。数字化时代&#xff0c;数据的流通与共享成为企业和团队之间日常运营的关键环节。内外网共享文件是指在内网和外网之间共享…

【知识学习】阐述Unity3D中动画渲染的概念及使用方法示例

Unity3D中的卡通渲染&#xff08;Cartoon Rendering&#xff09;是一种渲染技术&#xff0c;它模仿传统手绘动画或漫画的视觉效果。这种渲染风格通常具有鲜明的颜色、清晰的轮廓线和简化的光影效果&#xff0c;常用于制作动画、游戏和其他视觉媒体。 卡通渲染的基本概念 轮廓…

绩效管理过程中,定性指标的设计评价怎么做?

导读&#xff1a;企业在设计具体的定性考核标准时&#xff0c;可以运用一些量化的方式&#xff0c;使得最终的考核更具针对性和操作性&#xff0c;但是定性指标不可能完全量化分析&#xff0c;管理者不能过度追求量化原则&#xff0c;设计一些无效的指标。 绩效管理过程中&…

03逻辑门电路

分立门电路&#xff1a; 集成门电路&#xff1a; TTL门电路 MOS门电路&#xff1a;NMOS门电路、PMOS门电路、CMOS门电路 BICMOS门电路&#xff1a;CMOS的高输入阻抗和TTL的高放大倍数的结合 向更低功耗、更高速度发展 MOS管的Rdson在可变电阻区的阻值也一般会小于1000欧姆 …

django学习入门系列之第三点《伪类简单了解》

文章目录 hover&#xff08;伪类&#xff09;after&#xff08;伪类&#xff09;往期回顾 hover&#xff08;伪类&#xff09; 伪类指的是用冒号加的 hover样式指的是&#xff0c;当用户光标移动到设定区域后&#xff0c;所执行的用法 如&#xff1a; <!DOCTYPE html>…

并发编程工具集——Lock和Condition(上)(十二)

简述&#xff1a;Java SDK 并发包通过 Lock 和 Condition 两个接口来实现管程&#xff0c;其中 Lock 用于解决互斥问题&#xff0c;Condition 用于解决同步问题。 再造管程的理由和期望 理由&#xff1a;synchronized 没有办法解决“破坏不可抢占条件方案”。 原因是synchroniz…

Java NIO Buffer概念

针对每一种基本类型的 Buffer &#xff0c;NIO 又根据 Buffer 背后的数据存储内存不同分为了&#xff1a;HeapBuffer&#xff0c;DirectBuffer&#xff0c;MappedBuffer。 HeapBuffer 顾名思义它背后的存储内存是在 JVM 堆中分配&#xff0c;在堆中分配一个数组用来存放 Buffe…

springboot基于web模式的师资管理系统的设计与实现-计算机毕业设计源码040928

摘 要 随着互联网趋势的到来&#xff0c;各行各业都在考虑利用互联网将自己推广出去&#xff0c;最好方式就是建立自己的互联网系统&#xff0c;并对其进行维护和管理。在现实运用中&#xff0c;应用软件的工作规则和开发步骤&#xff0c;采用Java技术建设师资管理系统 。 本设…

全球视角下的AI安全挑战:面向未来的准备

云安全联盟大中华区即将推出人工智能安全认证专家&#xff08;Certified Artificial Intelligence Security Professional&#xff0c;CAISP&#xff09;培训及认证计划&#xff0c;将在Q3全面上线。 在全球科技创新的洪流中&#xff0c;人工智能&#xff08;AI&#xff09;无…

vue3.0(十五)状态管理vuex和pinia

文章目录 状态管理vuex1. 什么情况下应该使用 Vuex2. vuex的安装3. vuex的五大属性4. vuex的五大属性在组件中的应用5. 数据持久化 pinia1.Pinia 对比 Vuex2.安装及引入3.三大属性4.三大属性在组件中的运用总结 状态管理 状态管理是指对Vue.js应用中的数据进行统一管理的一种机…

【.Net】Web项目部署腾讯云

文章目录 总述前置准备docker-compose部署普通部署 参考 总述 前置准备 云服务添加端口 另有linux本身防火墙请参考&#xff1a; 【Linux】防火墙命令 需安装.Net SDK和Asp .Net Runtime 注意&#xff1a; 1、sdk也要不只是runtime 2、是Asp .Net Runtime不是.Net Runtime …