JVM运行时数据区

文章目录

  • JVM内存结构图
  • 1、运行时数据区域
    • JDK 1.7
    • JDK 1.8
    • 1. 线程栈(虚拟机栈)
    • 2. 本地方法栈
    • 3. 程序计数器
    • 4. 方法区(元空间)
    • 5. 堆
    • 6、运行时常量池(Runtime Constant Pool)
    • 7、直接内存(Direct Memory)
  • 2、JVM中对象及常量、局部变量、全局变量的存储位置
    • 1. 局部变量
    • 2. 全局变量
    • 3、JVM内存参数
  • 4、堆和栈的区别
  • 5、JVM对象创建过程
    • 5.1、类加载检查
    • 5.2、分配内存
      • 5.2.1、内存分配方式
      • 5.2.2、内存分配并发问题
    • 5.3、初始化零值
    • 5.4、设置对象头
    • 5.5 、执行初始化init方法

参考文章:

  1. JDK1.8 JVM运行时数据区域划分
  2. JVM内存结构

JVM内存结构图

在这里插入图片描述

1、运行时数据区域

Java 虚拟机在执行 Java 程序的过程中会把它管理的内存划分成若干个不同的数据区域。

JDK 1.8 和之前的版本略有不同,我们这里以 JDK 1.7 和 JDK 1.8 这两个版本为例介绍。

JDK 1.7

在这里插入图片描述

JDK 1.8

在这里插入图片描述
不同虚拟机的运行时数据区可能略微有所不同,但都会遵从 Java 虚拟机规范, Java 虚拟机规范规定的区域分为以下 5 个部分,其中线程私有程序计数器、虚拟机栈、本地方法栈;线程共享堆、方法区、直接内存

线程私有的

  • 程序计数器
  • 虚拟机栈
  • 本地方法栈

线程共享的

  • 方法区
  • 直接内存 (非运行时数据区的一部分)

Java 虚拟机规范对于运行时数据区域的规定是相当宽松的。以堆为例:堆可以是连续空间,也可以不连续。堆的大小可以固定,也可以在运行时按需扩展 。虚拟机实现者可以使用任何垃圾回收算法管理堆,甚至完全不进行垃圾收集也是可以的。

注意!!! jdk1.8里静态变量和字符串常量池放入堆中,运行时常量池仍然在方法区(元空间)

详情看这篇:字符串常量池

1. 线程栈(虚拟机栈)

    JVM的每一个线程对应一个线程栈,一个线程的每个方法会分配一块栈帧内存空间。**栈帧中包含:局部变量表、操作数栈、动态链接和方法出口**。

局部变量表:存储基本数据类型(int、float、byte等),如果是引用数据类型,则存储的是其在堆中的内存地址,也就是指向对象的一个指针。
操作数栈:操作数运算时一块临时的空间来存放操作数。
动态链接:将代码的符号引用转换为在方法区(运行时常量池)中的直接引用。
方法出口:存储了栈帧中的方法执完之后回到上一层方法的位置。

Java 虚拟机栈会出现两种错误:StackOverFlowError 和 OutOfMemoryError。

  • StackOverFlowError: 若 Java 虚拟机栈的内存大小不允许动态扩展,那么当线程请求栈的深度超过当前 Java 虚拟机栈的最大深度的时候,就抛出 StackOverFlowError 错误。
  • OutOfMemoryError: 若 Java 虚拟机栈的内存大小允许动态扩展,且当线程请求栈时内存用完了,无法再动态扩展了,此时抛出 OutOfMemoryError 错误。

2. 本地方法栈

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

方法执行完毕后相应的栈帧也会出栈并释放内存空间,也会出现 StackOverFlowError 和OutOfMemoryError 两种错误。

3. 程序计数器

程序计数器是用于存放下一条指令所在单元的地址的地方。当执行一条指令时,首先需要根据PC中存放的指令地址,将指令由内存取到指令寄存器中,此过程称为“取指令”。与此同时,PC中的地址或自动加1或由转移指针给出下一条指令的地址。此后经过分析指令,执行指令。完成第一条指令的执行,而后根据PC取出第二条指令的地址,如此循环,执行每一条指令。

程序计数器是线程私有的属性,其主要有两个作用:

  • 字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。
  • 在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了

4. 方法区(元空间)

线程共享的一块区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、运行时数据区等数据。

JDK 8 版本之后方法区(HotSpot 的永久代)被彻底移除了(JDK1.7 就已经开始了),取而代之是元空间,元空间使用的是直接内存。

另外,JDK1.8后,字符串常量池和静态变量存储到了堆中,类的元数据及运行时常量池存储到元空间中。

5. 堆

在这里插入图片描述

堆是运行时数据区,所有类的实例和数组都是在堆上分配内存。它在 JVM 启动的时候被创建。对象所占的堆内存是由自动内存管理系统也就是垃圾收集器回收。

Java 虚拟机所管理的内存中最大的一块,线程共享,此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。Java 堆是垃圾收集器管理的主要区域,因此也被称作GC 堆(Garbage Collected Heap)。

堆这里最容易出现的就是 OutOfMemoryError 错误,并且出现这种错误之后的表现形式还会有几种,比如:

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

6、运行时常量池(Runtime Constant Pool)

用于存放编译期生成的各种字面量和符号引用,当常量池无法再申请到内存时会抛出 OutOfMemoryError 错误。

  • JDK1.7之前运行时常量池逻辑包含字符串常量池存放在方法区, 此时hotspot虚拟机对方法区的实现为永久代

  • JDK1.7 字符串常量池被从方法区拿到了堆中, 这里没有提到运行时常量池,也就是说字符串常量池被单独拿到堆,运行时常量池剩下的东西还在方法区, 也就是hotspot中的永久代 。

  • JDK1.8 hotspot移除了永久代用元空间(Metaspace)取而代之, 这时候字符串常量池还在堆, 运行时常量池还在方法区, 只不过方法区的实现从永久代变成了元空间(Metaspace)

7、直接内存(Direct Memory)

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

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

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

2、JVM中对象及常量、局部变量、全局变量的存储位置

1. 局部变量

  • 基本数据类型:变量名和变量值存储在方法栈中。
  • 引用数据类型:变量值存储在方法栈中(存储的是堆中对象的内存地址),所指向的对象是存储在堆内存中(如new出来的对象)。

2. 全局变量

  • 基本数据类型:变量名和变量值存储在堆内存中。
  • 引用数据类型:变量名存储的是所引用对象的内存地址,变量名和变量值存储在堆内存中。

3、JVM内存参数

默认堆中年轻代(Young)占1/3,老年代(Old)占2/3,年轻代中包含Eden区和Survivor区,Survivor区包含From(S0)区和To(区),默认新生代中Eden区、From区、To区的比例为8:1:1,当Eden区内存不足时会触发Minor gc,没有被回收的对象进入到Survivor区,同时分代年龄+1,当再次触发Minor gc时,From区中的对象会移动到To区,Minor gc会回收Eden区和From区中的垃圾对象,对象的分代年龄会一次次的增加,当分代年龄增加到15以后,对象会进入到老年代。

当老年代内存不足时,会触发Full gc,如果Full gc无法释放足够的空间,会触发OOM内存溢出,在进行Minor gc或Full gc时,会触发STW(Stop The World),即停止用户线程。
在这里插入图片描述

Spring Boot程序的JVM参数设置格式(Tomcat启动直接加在bin目录下catalina.sh文件里):

java -Xms2048M -Xmx2048M -Xmn1024M -Xss512K -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M -jar microservice-eureka-server.jar
  • -Xss:每个线程的栈大小

  • -Xms:设置堆的初始可用大小,默认物理内存的1/64

  • -Xmx:设置堆的最大可用大小,默认物理内存的1/4

  • -Xmn:新生代大小

  • -XX:NewRatio:默认2表示新生代占年老代的1/2,占整个堆内存的1/3。

  • -XX:SurvivorRatio:默认8表示一个survivor区占用1/8的Eden内存,即1/10的新生代内存。

关于元空间的JVM参数有两个:-XX:MetaspaceSize=N-XX:MaxMetaspaceSize=N

-XX:MaxMetaspaceSize: 元空间最大值, 默认-1, 即不限制,或者说只受限于本地内存大小。

-XX:MetaspaceSize: 指定元空间触发Fullgc的初始阈值(元空间无固定初始大小), 以字节为单位,默认是21M左右,达到该值就会触发full gc进行类型卸载, 同时收集器会对该值进行调整: 如果释放了大量的空间, 就适当降低该值; 如果释放了很少的空间,会适当提高该值( 如果设置了-XX:MaxMetaspaceSize,不会超过其最大值 )。这个跟早期jdk版本的-XX:PermSize参数意思不一样,-XX:PermSize代表永久代的初始容量。

由于调整元空间的大小需要Full GC,这是非常昂贵的操作,如果应用在启动的时候发生大量Full GC,通常都是由于永久代或元空间发生了大小调整,基于这种情况,一般建议在JVM参数中将MetaspaceSize和MaxMetaspaceSize设置成一样的值,并设置得比初始值要大,对于8G物理内存的机器来说,这两个值可以都设置为256M。

4、堆和栈的区别

  • 物理地址
    • 堆的物理地址分配对对象是不连续的。因此性能慢些。
    • 栈使用的是数据结构中的栈,先进后出的原则,物理地址分配是连续的。所以性能快。
  • 内存分配
    • 堆因为是不连续的,所以分配的内存是在运行期确认的,因此大小不固定。一般堆大小远远大于栈。
    • 栈是连续的,所以分配的内存大小要在编译期就确认,大小是固定的。
  • 存放的内容
    • 堆存放的是对象的实例和数组。因此该区更关注的是数据的存储
    • 栈存放:局部变量,操作数栈,返回结果。该区更关注的是程序方法的执行。
  • 静态变量放在方法区,静态的对象还是放在堆。( × 静态变量1.8以后也在堆中 )
  • 程序的可见度
    • 堆对于整个应用程序都是共享、可见的。
    • 栈只对于线程是可见的。所以也是线程私有。他的生命周期和线程相同。

5、JVM对象创建过程

5.1、类加载检查

虚拟机遇到一条 new 指令时,首先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已被加载过、解析和初始化过。如果没有,那必须先执行相应的类加载过程。

5.2、分配内存

在类加载检查通过后,接下来虚拟机将为新生对象分配内存。对象所需的内存大小在类加载完成后便可确定,为对象分配空间的任务等同于把一块确定大小的内存从 Java 堆中划分出来。

5.2.1、内存分配方式

分配方式有 “指针碰撞” 和 “空闲列表” 两种,选择那种分配方式由 Java 堆是否规整决定,而 Java 堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。

在这里插入图片描述

5.2.2、内存分配并发问题

对象的创建在虚拟机中是一个非常频繁的行为,哪怕只是修改一个指针所指向的位置,在并发情况下也是不安全的,可能出现正在给对象 A 分配内存,指针还没来得及修改,对象 B 又同时使用了原来的指针来分配内存的情况。解决这个问题有两种方案:

对分配内存空间的动作进行同步处理(采用 CAS + 失败重试来保障更新操作的原子性);
把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在 Java 堆中预先分配一小块内存,称为本地线程分配缓冲(Thread Local Allocation Buffer, TLAB)。哪个线程要分配内存,就在哪个线程的 TLAB 上分配。只有 TLAB 用完并分配新的 TLAB 时,才需要同步锁。通过-XX:+/-UserTLAB参数来设定虚拟机是否使用TLAB。
在这里插入图片描述

5.3、初始化零值

内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),这一步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。

5.4、设置对象头

初始化零值完成之后,虚拟机要对对象进行必要的设置,例如这个对象是那个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息。 这些信息存放在对象头中。 另外,根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。

5.5 、执行初始化init方法

在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从 Java 程序的视角来看,对象创建才刚开始,在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从 Java 程序的视角来看,对象创建才刚开始, 方法还没有执行,所有的字段都还为零。所以一般来说,执行 new 指令之后会接着执行方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来

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

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

相关文章

云计算——虚拟化中的网络架构与虚拟网络(文末送书)

作者简介:一名云计算网络运维人员、每天分享网络与运维的技术与干货。 公众号:网络豆 座右铭:低头赶路,敬事如仪 个人主页: 网络豆的主页​​​​​ 目录 前期回顾 前言 一.网卡虚拟化 1.网卡虚拟化方法&…

基于 vue2 发布 npm包

背景:组件化开发需要,走了一遍发布npm包的过程,采用很简单的模式实现包的发布流程,记录如下。 项目参考:基于vue的时间播放器组件,并发布到npm_timeplay.js_xmy_wh的博客-CSDN博客 1、项目初始化 首先&a…

C语言练习题解析:挑战与突破,开启编程新篇章!(1)

💓博客主页:江池俊的博客⏩收录专栏:C语言刷题专栏👉专栏推荐:✅C语言初阶之路 ✅C语言进阶之路💻代码仓库:江池俊的代码仓库🎉欢迎大家点赞👍评论📝收藏⭐&a…

Vue项目直接报错

最近自己在做一个vue2项目,vue并不熟悉,所以求解!!! 通过命令:vue create app 创建项目,但打开后,浏览器直接报错,意思为:不能在模块外使用import语句(at ho…

多维时序 | MATLAB实现SABO-CNN-GRU-Attention多变量时间序列预测

多维时序 | MATLAB实现SABO-CNN-GRU-Attention多变量时间序列预测 目录 多维时序 | MATLAB实现SABO-CNN-GRU-Attention多变量时间序列预测预测效果基本介绍模型描述程序设计参考资料 预测效果 基本介绍 多维时序 | MATLAB实现SABO-CNN-GRU-Attention多变量时间序列预测。 模型描…

框架分析(5)-Django

框架分析(5)-Django 专栏介绍Django核心概念以及组件讲解模型(Model)视图(View)模板(Template)路由(URLconf)表单(Form)后台管理&…

vue中bus的使用和涉及到的问题

创建一个js文件 import Vue from "Vue" export default new Vue 我们可以直接在要使用的页面中引用使用 import bus from /assets/js/eventBus.js;bus.$emit("info", "123") // 使用bus.$on("info", (val) > { // 接收console.l…

【【萌新的STM32学习20--按键输入实验】】

萌新的STM32学习20–按键输入实验 在了解完蜂鸣器的实验之后我们又进行一个小实验 又是正点原子没有上课讲解的实验 对于这种不平滑的情况我们更多的也会去选择方法进行消除抖动 软件消抖:方法很多,我们例程中使用最简单的延时消抖。检测到按键按下后&…

GitLab启动失败:fail: alertmanager: runsv not running

问题描述 sudo gitlab-ctl restart ,报错如下 : summergaoubuntu:/etc/gitlab$ sudo gitlab-ctl start fail: alertmanager: runsv not running fail: gitaly: runsv not running fail: gitlab-exporter: runsv not running fail: gitlab-workhorse: …

【JavaWeb 专题】15个最经典的JavaWeb面试题

文章目录 HTTP长连接和短连接HTTP/1.1 与 HTTP/1.0 的区别可扩展性缓存带宽优化长连接消息传递Host 头域错误提示 AjaxAjax 的优势: JSP 和 servlet 有什么区别?定义区别 JSP 的9大内置对象及作用JSP 的 4 种作用域?session 和 cookie 有什么…

ATA-2161高压放大器的电子实验案例(案例合集)

ATA-2161是一款理想的可放大交直流信号的单通道高压放大器。最大差分输出1600Vp-p(800Vp)高压,可以驱动高压型负载。凭借其优异的指标参数受到不少电子工程师的喜欢,其在电子实验中的应用也非常频繁,下面为大家整理出ATA-2161高压放大器的应用…

代码随想录打卡—day46—【DP】— 8.29 背包END

1 139. 单词拆分 139. 单词拆分 做了很久...估计2h 一开始我的思路卡死了 看题解之后的思路的详解见注释, 我的写法和carl 答案在一些微小的细节上略有不同,我的更好理解,但他的解法更简单。 我写的过程中,需要注意下标和字符…

为何直线导轨要保持日常清洁?

随着时代的发展,已逐步从传统的工业发展模式发展到工业自动化,直线滑轨在工业生产中得到了广泛的应用,大大提高了生产效率,带动了经济效益的增长。 众所周知,想要直线导轨的使用达到预期的效果,日常的保养和…

【论文笔记】Planning and Decision-Making for Autonomous Vehicles

文章目录 Summary1. INTRODUCTION2. MOTION PLANNING AND CONTROL2.1. Vehicle Dynamics and Control2.2. Parallel Autonomy2.3. Motion Planning for Autonomous Vehicles 3. INTEGRATED PERCEPTION AND PLANNING3.1. From Classical Perception to Current Challenges in Ne…

1688API技术解析,实现获得1688商品详情

要实现获得1688商品详情,你需要使用1688 API。1688 API是阿里巴巴旗下的开放平台,它提供了一套丰富的接口,可以让开发者通过编程的方式获取到1688网站上的商品信息。 首先,你需要在阿里开放平台注册一个账号,并创建一…

C++中数组作为参数进行传递方法

文章目录 基础:数组作为函数形参示例:1、一维数组的传递(1)直接传递(2)指针传递(3)引用传递 2、二维数组的传递(1)直接传递(2)指针传递…

Java中word转Pdf工具类

背景: 最近做的一个项目中,对于word转Pdf用的地方很多,特此记录 搭建总图: 代码部分: 1.需要的jar包: aspose-words-15.8.0-jdk16.jar 注:下载好这个jar包后,在项目的根目录新建一…

第一个react应用程序并添加样式

编写第一个react应用程序 将目录下的文件、src文件夹、public文件夹清空&#xff0c;项目根目录下新建一个文件index.js 在文件中写入以下代码 import React from react import ReactDOM from react-dom ReactDOM.render(<h1>欢迎进入React的世界</h1>,document.…

利用fsimage分析HDFS小文件

一、Hive 小文件概述 在Hive中&#xff0c;所谓的小文件是指文件大小远小于HDFS块大小的文件&#xff0c;通常小于128 MB&#xff0c;甚至更少。这些小文件可能是Hive表的一部分&#xff0c;每个小文件都包含一个或几个表的记录&#xff0c;它们以文本格式存储。 Hive通常用于…

clickhouse(十四、分布式DDL阻塞及同步阻塞问题)

文章目录 一、分布式ddl 阻塞、超时现象验证方法解决方案 二、副本同步阻塞现象验证解决方案 一、分布式ddl 阻塞、超时 现象 在clickhouse 集群的操作中&#xff0c;如果同时执行一些重量级变更语句&#xff0c;往往会引起阻塞。 一般是由于节点堆积过多耗时的ddl。然后抛出…