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

news2024/11/26 9:13:15

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。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

# 音频处理4_傅里叶变换

1.离散傅里叶变换 对于离散时域信号 x[n]使用离散傅里叶变换&#xff08;Discrete Fourier Transform, DFT&#xff09;进行频域分析。 DFT 将离散信号 x[n] 变换为其频谱表示 X[k]&#xff0c;定义如下&#xff1a; X [ k ] ∑ n 0 N − 1 x [ n ] e − j 2 π k n N X[k]…

Qt 使用代码布局,而不使用UI布局

一、工程的建立&#xff1a; 1、打开Qt Creator&#xff0c;文件&#xff0c;新建文件或项目 2、选择Application&#xff0c;Qt Widgets Application 3、写入名称&#xff0c;选择qmake 4、选择基类Base class&#xff0c;去除Generate form 务必选择QWidget&#xff0c;若…

读AI新生:破解人机共存密码笔记14逆强化学习算法

1. 数学保证 1.1. 如果我们要沿着新的路线重建人工智能&#xff0c;那么它的基础必须是坚实的 1.2. 通过精确的定义和一步步的严格数学证明来提供无可辩驳的保证 1.3. 希望证明一个定理&#xff1a;设计人工智能系统的一种特殊方式可以确保它…

Linux如何安装openjdk1.8

文章目录 Centosyum安装jdk和JRE配置全局环境变量验证ubuntu使用APT(适用于Ubuntu 16.04及以上版本)使用PPA(可选,适用于需要特定版本或旧版Ubuntu)Centos yum安装jdk和JRE yum install java-1.8.0-openjdk-devel.x86_64 安装后的目录 配置全局环境变量 vim /etc/pr…

Python | Leetcode Python题解之第201题数字范围按位与

题目&#xff1a; 题解&#xff1a; class Solution:def rangeBitwiseAnd(self, m: int, n: int) -> int:while m < n:# 抹去最右边的 1n n & (n - 1)return n

spring-boot-starter-json配置对象属性为空不显示

问题背景 在Spring Boot中使用spring-boot-starter-json&#xff08;通常是通过jackson实现的&#xff09;时&#xff0c;如果你希望在序列化对象时&#xff0c;如果某个属性为空&#xff0c;则不显示该属性&#xff0c;你可以使用JsonInclude注解来实现这一点。 pom.xml <…

net Framework OAuth2.0

grant_type client_credentials 客户端凭证password 密码模式 用于资源所有者密码凭据token 隐藏式 、 简化式 简化模式又称为隐式授权码模式&#xff0c;它是授权码模式的一个简化版本authorization_code 授权码 A. 第三方程序向资源拥有者(用户)发送授权请求&#xf…

如何获得更高质量的回答-chatgpt

在与技术助手如ChatGPT进行交互时&#xff0c;提问的方式直接影响到你获得的答案质量。以下是几个关键的提问技巧&#xff0c;可以帮助你在与ChatGPT的互动中获得更有效的回答&#xff1a; 1. 清晰明了的问题 技巧&#xff1a;确保问题清晰明了&#xff0c;避免含糊不清或模糊的…

【2024最新华为OD-C/D卷试题汇总】[支持在线评测] 特殊加密算法(200分) - 三语言AC题解(Python/Java/Cpp)

&#x1f36d; 大家好这里是清隆学长 &#xff0c;一枚热爱算法的程序员 ✨ 本系列打算持续跟新华为OD-C/D卷的三语言AC题解 &#x1f4bb; ACM银牌&#x1f948;| 多次AK大厂笔试 &#xff5c; 编程一对一辅导 &#x1f44f; 感谢大家的订阅➕ 和 喜欢&#x1f497; &#x1f…

一键智控,舒适无限:网关在风机盘管智能温控中的应用

风机盘管智能控制系统采用钡铼技术系列无线网关&#xff0c;搭配各类风机设备及传感器组成无线物联中央空调室内机管理系统&#xff0c;实现整个办公楼的空调环境智能化管理。在建筑舒适度的前提下&#xff0c;降低能耗&#xff0c;避免能源浪费。 网关通信接口采用无线传输的…

vscode刷LeetCode算法题环境配置

首先&#xff0c;下载nodejs 在vscode中安装LeetCode插件 安装好进行配置 选择leetcode-cn 填上刚才下载node.exe的路径 完成之后重启一下vscode 重启之后登陆LeetCode 完成之后就可以看到题目了 点击 code now 就可以开始刷题了

Flutter循序渐进==>第一个界面

导言 const MyApp({Key? key}) : super(key: key); const: 这个关键字表示这是一个编译时常量构造函数。当一个类使用 const 构造函数初始化时&#xff0c;它的所有字段都将被设置为编译时常量&#xff0c;并且该对象将在编译时就被创建出来。这对于状态不变&#xff08;immu…

基于单片机和LabVIEW 的远程矿井水位监控系统设计

摘要 &#xff1a; 针 对 现 有 矿 井 水 位 监 控 系 统 存 在 结 构 复 杂 和 不 能 远 程 监 控 的 问 题 &#xff0c; 设计了基于单片机和&#xff2c;&#xff41;&#xff42;&#xff36;&#xff29;&#xff25;&#xff37; 的远程矿井水位监控系统 &#xff0c; 详…

python-(opencv)视频转glf

文章目录 前言python-(opencv)视频转glf1. 下载 opencv-python2. cv2&#xff08;OpenCV&#xff09;和imageio的区别3. demo源码 前言 如果您觉得有用的话&#xff0c;记得给博主点个赞&#xff0c;评论&#xff0c;收藏一键三连啊&#xff0c;写作不易啊^ _ ^。   而且听说…

uView 2.0:uni-app生态的利剑出鞘,引领UI框架新纪元

引言 随着移动互联网的快速发展&#xff0c;跨平台应用开发成为了开发者们关注的焦点。uni-app&#xff0c;一个基于Vue.js的跨平台应用开发框架&#xff0c;因其高效、易用的特性而广受欢迎。在uni-app的生态系统中&#xff0c;UI框架的选择对于开发者而言至关重要。今天&…

2024上海CDIE 参展预告 | 一站式云原生数字化平台已成趋势

为什么企业需要进行数字化转型&#xff1f;大家都在讨论的数字化转型面临哪些困境&#xff1f;2024.6.25-26 CDIE数字化创新博览会现场&#xff0c;展位【A18】&#xff0c;期待与您相遇&#xff0c;共同探讨企业如何利用数字化技术驱动业务增长。 一、展会介绍——CDIE数字化…

C语言 | Leetcode C语言题解之第201题数字范围按位与

题目&#xff1a; 题解&#xff1a; int rangeBitwiseAnd(int m, int n) {while (m < n) {// 抹去最右边的 1n & (n - 1);}return n; }

【Linux操作系统】进程地址空间与动态库加载

当系统执行一个依赖动态库的可执行程序时&#xff0c;系统不仅要将该可执行程序加载到内存中还要由加载器将动态库加载到内存中&#xff08;静态库没有&#xff09;&#xff0c;因此必须要让加载器知道该动态库的名称&#xff0c;系统会默认在/lib64路径下查找&#xff0c;解决…

Java将list数组中重复的对象进行去重

/*** 数组去重*/ public class ArrayDistinct {public static void main(String[] args) {ArrayList<Object> list new ArrayList<>();JSONObject jsonObject1 new JSONObject();jsonObject1.put("name","张三");jsonObject1.put("age&…

Vite: 关于Rollup打包

概述 Rollup 是一款基于 ES Module 模块规范实现的 JavaScript 打包工具&#xff0c;在前端社区中赫赫有名&#xff0c;同时也在 Vite 的架构体系中发挥着重要作用不仅是 Vite 生产环境下的打包工具&#xff0c;其插件机制也被 Vite 所兼容&#xff0c;可以说是 Vite 的构建基…