【Java虚拟机学习1】JVM运行时数据区

news2024/11/27 14:48:39

JVM运行时数据区

一、前言

我们知道Java程序是运行在JVM(Java虚拟机)上的,Java程序运行时会占用一定的内存,在虚拟机自动管理机制的帮助下,不再需要为每一个new操作去写配对的delete/free代码,不容易出现内存泄漏和内存溢出的问题,看起来由虚拟机管理内存一切都很美好。不过,也正式因为Java程序员把控制内存的权力交给了Java虚拟机,一旦出现内存泄漏和溢出方面的问题,如果不了解虚拟机是怎样使用内存的,那排查错误、修正问题将成为一项异常艰难的工作。所以,今天这篇文章我们来简答为大家介绍一下Java虚拟机在运行时的数据区都有哪些。

二、JVM运行时数据区概述介绍

在这里插入图片描述
Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域有各自的用途,以及创建时间和销毁的时间。有的区域随着虚拟机进程的启动而一直存在,有些区域则是依赖用户线程的启动和结束而建立和销毁。

根据生命周期作用域可以分为两种:线程共享的区域线程私有的区域
其中:

  • 线程共享的区域方法区,这些区域的生命周期和Java程序的生命周期一致
  • 线程私有的区域虚拟机栈、本地方法栈、程序计数器,这些区域的生命周期随着用户线程的启动和结束而创建和销毁。

三、JVM运行时数据区详细介绍

(一)程序计数器

程序计数器是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器

因为操作系统的 CPU 核数就那么多,在 Java 虚拟机中的想要多线程执行就需要轮流切换执行,被分配到处理器执行时间的线程才会执行。所以就需要程序计数器记录执行位置,等线程切换后可以找到字节码上次的执行位置继续执行。各个线程都有自己的程序计数器,所以这块是线程私有的。并且程序计数器也是唯一不会发生内存溢出(OutOfMemoryError)的区域。并且程序计数器的生命周期随着线程的创建而创建,随着线程的结束而死亡。
程序计数器的主要有两个作用:
(1)字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制。如顺序执行、选择、循环、异常处理。
(2)在多线程情况下,程序计数器用于记录当前线程执行的位置,从而确定当前线程被切换回来的时候知道上次执行到了哪。

程序计数器在记录当前执行的Java方法和本地方法有什么区别:

  • 在执行Java方法和本地方法时,程序计数器记录的数值是有区别的。如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的程序字节码指令的地址。如果执行的是本地方法,这个计数器的值则为空null。

我们思考这样的一个问题:如果本地方法记录的是空值,线程恢复后怎么才能找到正确的位置呢?(重点思考!!!!)
在Java虚拟机中,线程在启动的时候会创建一个虚拟机栈,每个线程都有一个独立的虚拟机栈。每当线程方法调用的时候,就会往栈中压入一个栈帧,栈帧中存储了当前方法需要用到的一些数据以及当前执行的字节码指令的位置等信息。当线程执行完毕一个方法后,就会弹出这个方法对应的栈帧,然后恢复上一个方法的栈帧。

当线程在执行本地方法时,虚拟机并不会使用Java堆栈帧来存储本地方法执行的相关信息,而是使用了一种名为JNI(Java Native Interface)的机制来处理。JNI允许Java程序调用本地语言编写的代码,并传递参数和返回值。在JNI的实现中,Java虚拟机会为每个本地方法的调用去创建一个独立的栈帧,本地方法栈的栈帧中包含了本地方法执行时所需要的一些数据,例如函数调用的参数、返回值等。当本地方法执行完毕后,Java虚拟机就会清除对应的本地方法栈帧。

所以当本地方法执行完毕后,Java虚拟机会根据返回的值来判断程序接下来需要执行哪一条字节码指令,并将计数器设置为对应的值,然后继续执行Java程序。因此,即使程序计数器在本地方法执行期间被设置为null,在本地方法执行完毕后,Java虚拟机还是可以通过其他方式来回复正确的程序计数器,并继续执行对应的Java程序字节码。

(二)虚拟机栈

虚拟机栈描述的是Java方法执行的线程内存模型。与程序计数器一样,Java 虚拟机栈(后文简称栈)也是线程私有的,它的生命周期和线程相同,随着线程的创建而创建,随着线程的死亡而死亡。

每个Java方法被执行的时候,Java虚拟机都会同步创建一个栈帧,并将栈帧压入栈中,每个方法调用结束后,都会有一个栈帧被弹出
栈由一个个的栈帧组成,用于存储Java方法相关的局部变量表、操作数栈、动态链接、方法返回地址。和数据结构上的栈类似,两者都是先进后出的数据结构,只支持入栈和出栈两种操作。

所有Java方法的调用都是通过Java虚拟机栈来实现的(需要和其他运行时数据区配合比如程序计数器),而对于一些本地方法的调用则是通过本地方法栈实现的。
在这里插入图片描述

其中:

  • 局部变量表:用于存放Java方法编译期可知的各种数据类型的参数、对象引用(reference类型,它不同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)。
    在这里插入图片描述
  • 操作数栈:主要用于存放Java方法执行过程中所产生的中间结果及临时变量。
  • 动态链接:主要是用于将符号引用转换为调用方法的直接引用。动态链接服务与一个方法调用其他方法的场景。(class文件的常量池里保存大量的符号引用比如:方法引用的符号引用,当一个方法调用其他方法,需要将常量池中指向方法的符号引用转换为其在内存地址中的直接引用。)
  • 返回地址:指当前方法执行结束后返回到上一个方法继续执行的指令地址。(方法退出分为两种情况:正常退出和异常退出;正常退出的话就按照返回地址正常恢复到调用位置,异常退出需要搜索本方法异常表进行匹配(就是方法内是否 catch 异常),没有匹配到就不会将返回地址返回。)

问题思考:程序运行过程中,虚拟机栈可能会抛出哪些错误?

虚拟机栈空间虽然不是无限的,但一般正常情况下是不会出现问题的。但是如果函数调用陷入无限循环的话,就会导致栈中被压入太多的栈帧而占用太多空间,进而导致栈空间过深。当线程请求栈的深度超过当前Java虚拟机栈的最大深度时候,就会抛出StackOverFlowError错误。(StackOverFlowError是一种error异常,表示虚拟机栈空间已满,无法再进行方法的调用)

Java方法有两种返回方式:一种是return正常返回,另一种是抛出异常。不管哪种返回方式都会导致栈帧被弹出。也就是说,栈帧随着方法调用而创建,随着调用结束而销毁。无论方法正常完成还是异常完成都算作方法结束。

除了StackOverFlowError错误之外,栈还有可能出现OutOfMemoryError错误,这是因为如果栈的内存大小可以动态扩展,如果虚拟机在动态扩展虚拟机栈的内存大小空间时且无法申请到足够的内存空间,则抛出OutofMemoryError异常。

综上所述:程序运行过程中,虚拟机栈可能会抛出两种错误(error),分别为:StackOverFlowError栈空间已满、OutOfMemoryError内存溢出

  • StackOverFlowError错误:函数调用陷入无限循环时,线程请求栈的深度超过Java虚拟机栈的最大深度时,就抛出StackOverFlowError错误。
  • OutOfMemoryError错误:如果栈的内存大小可以动态扩展,当虚拟机栈在动态扩展时且在虚拟机上无法申请到足够的内存空间,则抛出OutOfMemoryError错误。

(三)本地方法栈

和虚拟机栈发挥的作用非常相似,区别是:虚拟机栈为虚拟机执行的Java方法(也就是字节码)服务,而本地方法栈则为虚拟机执行本地方法服务。
在HotSpot虚拟机中将本地方法栈和虚拟机栈合二为一。

本地方法被执行的时候,在本地方法栈也会去创建一个栈帧,用于存储该本地方法的局部变量表、操作数栈、动态链接、出口信息。

本地方法执行完毕后栈帧也会出栈并释放内存空间,也会出现StackOverFlowErrorOutOfMemoryError错误。

(四)堆

堆是Java虚拟机所管理的内存中最大的一块,Java堆是所有线程共享的一块内存区域,在虚拟机启动时创建(即Java程序启动时创建,生命周期和Java程序一致)Java堆的唯一目的就是存放对象实例,几乎所有的对象实例都在堆里分配内存。

Java堆是垃圾收集器管理的主要区域,因此也被称作GC堆(Garbage Collected Heap),从垃圾回收的角度,由于现在收集器都采用分代垃圾收集算法,所以Java堆的内存还可以细分为:新生代、老年代;再细一点有:Eden、Survivor、Old等空间。进一步划分的目的是为了更好的回收内存或是更快的分配内存。

下图所示的 Eden 区、两个 Survivor 区 S0 和 S1 都属于新生代,中间一层属于老年代,最下面一层属于永久代。
JDK 8 版本之后 PermGen(永久代) 已被 Metaspace(元空间) 取代元空间使用的是本地内存

在这里插入图片描述
堆这里最容易出现的就是OutOfMemoryError错误,并且出现这种错误后的表现形式还会有如下几种:

  • java.lang.OutOfMemoryError: GC Overhead Limit Exceeded:当 JVM 花太多时间执行垃圾回收并且只能回收很少的堆空间时,就会发生此错误
  • java.lang.OutOfMemoryError: Java heap space :假如在创建新的对象时, 堆内存中的空间不足以存放新创建的对象, 就会引发此错误。(和配置的最大堆内存有关,且受制于物理内存大小)

(五)方法区

方法区属于JVM运行时数据区的一块逻辑区域,方法区是线程共享的,生命周期和Java程序一致,用来存储已被虚拟机加载的 类信息、字段信息、方法信息、常量、静态变量、即时编译器编译后的代码缓存等数据( 即HotSpot 著名的 JIT 即时编译技术的热点代码缓存等数据)。

问题1:方法区和永久代以及元空间是什么关系呢?
方法区和永久代以及元空间的关系很像 Java 中接口和类的关系,类实现了接口,这里的类就可以看作是永久代和元空间,接口可以看作是方法区,也就是说永久代以及元空间是 HotSpot 虚拟机对虚拟机规范中方法区的两种实现方式。并且,永久代是 JDK 1.8 之前的方法区实现,JDK 1.8 及以后方法区的实现变成了元空间

在这里插入图片描述
问题2:永久代和元空间的区别是什么?说说对永久代和元空间的理解
永久代以及元空间是 HotSpot 虚拟机对虚拟机规范中方法区的两种实现方式
在 JDK1.7 及 JDK1.7 之前的版本,JVM 是通过堆中的永久代实现方法区的,容易导致内存溢出;而 1.7 版本,将原来在永久代的静态变量字符串常量池(为了提升性能和减少内存消耗为常量字符串单独开辟的空间)移到了堆空间里;到了JDK1.8,JVM 完全放弃永久代选择使用元空间来实现方法区,直接使用本地内存(Native Memory)

JDK1.8 版本 静态变量、字符串常量池、类信息与运行时常量池(存放类加载生成的字面量和符号引用)内存布局:
在这里插入图片描述

问题3:为什么要将永久代 (PermGen) 替换为元空间 (MetaSpace) 呢?
1、永久代内存大小固定(整个永久代有一个 JVM 本身设置的固定大小上限),无法进行调整,而元空间使用的是本地内存,受本机可用内存的限制,虽然元空间仍旧可能溢出,但是比原来出现的几率会更小。
2、永久代垃圾回收速率低。永久代的内部存储着各个类的相关信息,所以对永久代的某些对象进行垃圾回收时,可能需要触发Full GC进行一次全面扫描。而元空间则采用新的垃圾回收机制:指针碰撞或者空闲列表的方式进行分配和回收,可以有效的降低Full GC的频率,提高垃圾回收效率。
3、元空间可以加载类的数据更多。元空间里面存储的是类的元数据,这样加载多少类元素就不由MaxPermSize控制了,而由系统的实际可用空间来控制,这样能加载的类就更多了。

另外,我们还需要关注如下几个概念

(一)运行时常量池

运行时常量池是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池表,用于存放编译期生成的各种字面量与符号引用,这部分内容将在类加载后存放到
方法区的运行时常量池中。
既然运行时常量池是方法区的一部分,自然受到方法区内存的限制,当常量池无法再申请到内存时会抛出 OutOfMemoryError 错误。

(二)字符串常量池

字符串常量池 是 JVM 为了提升性能和减少内存消耗针对字符串(String 类)专门开辟的一块区域,主要目的是为了避免字符串的重复创建。

JDK1.7 之前,字符串常量池存放在永久代。JDK1.7 字符串常量池和静态变量从永久代移动了 Java 堆中。
在这里插入图片描述

在这里插入图片描述
问题:JDK1.7为什么要将字符串常量池移动到堆中?
1、提高垃圾回收效率。 主要是因为永久代(方法区实现)的 GC 回收效率太低,只有在整堆收集 (Full GC)的时候才会被执行 GC垃圾回收。Java 程序中通常会有大量的被创建的字符串等待回收,将字符串常量池放到堆中,能够更高效及时地回收字符串内存。
2、降低了OutOfMemoryError错误的出现。 字符串常量池移入堆中后,可根据需求动态扩展,减少了OutOfMemoryError错误的出现。

(三)直接内存

直接内存是一种特殊的内存缓冲区,并不在 Java 堆或方法区中分配的,而是通过 JNI 的方式在本地内存上分配的。

直接内存并不是虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域,但是这部分内存也被频繁地使用。而且也可能导致 OutOfMemoryError 错误出现。

直接内存的分配不会受到 Java 堆的限制,但是,既然是内存就会受到本机总内存大小以及处理器寻址空间的限制。

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

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

相关文章

指针进阶详解(下)

指针进阶详解(下) 前言1. 函数指针1.1 两端有趣代码 2. 函数指针数组2.1 函数指针数组的用途之一:转移表 3. 指向函数指针数组的指针4. 回调函数5. 结尾 前言 在指针进阶详解(上)中,我们已经介绍了部分指针…

基于数据安全的风险评估(三):风险分析与评估

完成了资产识别、脆弱性识别及威胁识别后,我们可以采用适当的方法和工具确定威胁利用脆弱性导致安全事件发生的可能性。综合安全事件作用资产价值及脆弱性的严重程度,判断事件造成的损失及对组织的影响,即安全风险。 一 风险计算形式及关键环…

HTTP协议对比HTTPS协议

HTTP协议对比HTTPS协议 1. HTTP协议1.1 概述1.2 HTTP协议格式1.3 HTTP协议支持的方法1.3.1 GET方法1.3.2 POST方法1.3.3 其他HTTP方法1.3.4 GET对比POST 1.4 请求报文1.4.1 Content-Type:请求体中数据格式1.4.2 Cookie:浏览器缓存 1.5 响应报文1.5.1 状态…

LED驱动(原始架构优化:分层/分离)——STM32MP157

文章目录 优化思想:分层Demo的LED驱动程序led_opr.hboard_demo.cleddrv.cledtest.cMakefile编译测试 STM32MP157的LED驱动程序board_stm32mp157.cleddrv.cled_opr.hMakefiel编译测试 优化思想:分离Demo的LED驱动程序led_resource.hboard_A_led.cchip_dem…

MacOS系统(M1/M2)安装AI绘画StableDiffusion保姆级教程

TOC 安装完成后,推荐阅读这篇教程:AI绘画:Stable Diffusion 终极炼丹宝典:从入门到精通 实操环境: macOS 13 Arm64(建议12以上的系统使用) Apple M1 先来看几个样例: AI绘画S…

goland自定义代码模版

在 GoLand 中我们既可以修改已有代码模版,也可以新建属于我们自己的代码模版,按需配置。 一、修改已有代码模版 现在代码编辑页输入你想更改的代码模版,然后点击右下角的小灯泡按钮,接着选中编辑活动模版设置即可。 二、新建…

[机缘参悟-99] :关于局部最优与全局最优解的人生感悟

在没有获取全局信息之前,要获得全局最优解几乎是不可能的,最多是概率大一点而已,大多数时候,由于时空资源的限制,获得往往是局部最优解,局部最优解,放在全局,往往并非全局最优&#…

数学专题训练3 数论1

1. Problem - 27E - Codeforces 给定 n ( 1 ≤ n ≤ 1000 ) n(1 \le n \le 1000) n(1≤n≤1000)​​​,找到因子个数恰好为 n n n​​​ 个的最小正整数. 保证答案不大于 1 e 18 1e18 1e18. 和 反素数 的思路是一样的,深搜 这个是枚举当前数字可以…

Shiro反序列化漏洞(CVE-2016-4437)+docker靶场+工具利用

一、Shiro反序列化漏洞-CVE-2016-4437原理 将java对象转换为字节序列(json/xml)的过程叫序列化,将字节序列(json/xml)恢复为java对象的过程称为反序列化。 Shiro框架提供了“记住我”的功能,用户登陆成功…

JPA 概述及常用注解详解、SpringDataJpa 使用指南

JPA(Java Persistence API)是 Java 标准中的一套ORM规范(提供了一些编程的 API 接口,具体实现由 ORM 厂商实现,如Hiernate、TopLink 、Eclipselink等都是 JPA 的具体实现),借助 JPA 技术可以通过…

Unity3D 入门

文章目录 拖拽快捷键资源商店地形构建器 拖拽快捷键 首先看到Scene界面,我们布置游戏场景中的游戏对象基本是在这个界面完成的 鼠标滚轮键按住:能够拖拽平面,不移动对象鼠标右键按住:能够旋转我们观察界面的视角,不移…

快速理解并实现权限控制

什么是权限控制? 权限控制是指在一个系统或应用中对用户或角色的操作进行限制和管理的过程。它用于确保只有经过授权的用户或角色能够执行特定的操作或访问特定的资源。权限控制是信息安全和访问控制的重要组成部分。 权限控制的主要目的是保护系统的安全性和完整性&#xf…

GEE:支持向量机(SVM)分类参数说明和官方案例

作者:CSDN @ _养乐多_ 本文记录了在GEE平台上进行支持向量机(SVM)分类使用ee.Classifier.libsvm的说明和官方案例。 文章目录 一、函数介绍1.1 GEE上说明1.2 函数说明二、官方案例一、函数介绍 1.1 GEE上说明 ee.Classifier.libsvm(decisionProcedure, svmType, kernelTyp…

CentOS5678 repo源 地址 阿里云开源镜像站

CentOS5678 repo 地址 阿里云开源镜像站 https://mirrors.aliyun.com/repo/ CentOS-5.repo https://mirrors.aliyun.com/repo/Centos-5.repo [base] nameCentOS-$releasever - Base - mirrors.aliyun.com failovermethodpriority baseurlhttp://mirrors.aliyun.com/centos/$r…

Springboot JPA 集成多租户

背景: ​ iot-kit项目用的是jpa,不是mybatis,项目中需要引入多租户 参考文章: 【讲解多租户的实现与原理】 https://www.bilibili.com/video/BV1F84y1T7yf/?share_sourcecopy_web&vd_source981718c4abc87423399e43793a5d3…

pytest实现用例间参数传递的方式

pytest实现用例间参数传递的方式 一、通过conftest创建全局变量二、使用tmpdir_factory方法 我们在做接口自动化测试的时候,会经常遇到这种场景:接口A的返回结果中的某个字段,是接口B的某个字段的入参。如果是使用postman,那我们可…

前端精度丢失处理

前端操作数据时,如果数据超出一定范围会出现精度丢失的问题,这是因为,在传输过程中,数据类型被转换成Number,Number的精度范围在2^53之间,即 -9007199254740991 ~ 9007199254740991,超出范围就会…

Unity游戏源码分享-迷你高尔夫球游戏MiniGolfConstructionKitv1.1

Unity游戏源码分享-迷你高尔夫球游戏MiniGolfConstructionKitv1.1 有多个游戏关卡 工程地址:https://download.csdn.net/download/Highning0007/88052881

Unity游戏源码分享-射酒瓶游戏Demo

Unity游戏源码分享-射酒瓶游戏Demo 工程地址:https://download.csdn.net/download/Highning0007/88052883

财务报表制作:哪些软件值得推荐?

当今,企业需要准确、及时地制作各种会计报表,以便管理者更好地掌握财务状况。然而,使用传统的纸质方式进行制表常常会出现复杂、繁琐等问题,降低制表效率。因此,使用会 计软件成为了制表的首选。 传统制作财务报表的方…