【JVM】详解JVM的五大内存模型、可能出现的异常以及堆栈引用易错点

文章目录

  • 1、堆(线程共享)
  • 2、方法区(线程共享)
  • 3、虚拟机栈(线程私有)
  • 4、本地方法栈(线程私有)
  • 5、程序计数器(线程私有)
  • 6、易错点

在这里插入图片描述
源自:深入理解Java虚拟机:JVM高级特性与最佳实践(第3版) 周志明

1、堆(线程共享)

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

Java堆既可以被实现成固定大小的,也可以是可扩展的,不过当前主流的Java虚拟机都是按照可扩
展来实现的(通过参数-Xmx和-Xms设定)。如果在Java堆中没有内存完成实例分配,并且堆也无法再
扩展时,Java虚拟机将会抛出OutOfMemoryError异常。原因有二:

  1. JVM堆内存设置不够。可以通过-Xms、-Xmx来调整。
  2. 代码中创建了大量大对象,并且不能被垃圾收集器收集(存在被引用)

2、方法区(线程共享)

方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。此区域的数据是不会被垃圾回收器回收掉的,关闭JVM才会释放此区域所占有的内存。
当方法区无法满足内存分配需求时,抛出OutOfMemoryError异常。

3、虚拟机栈(线程私有)

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

这里需要关注一下入栈的过程:
如果方法里声明了基本数据类型(byte、short、int、long、double、float、char、boolean)的变量,那么它们都存储在栈中;如果方法中new了新的对象,那么会先去堆中创建该对象,然后栈中存储该对象的引用地址。如果方法中引用了已创建的对象,那么栈中存储该对象的引用地址。

如果某个线程的线程栈的内存被耗尽,没有足够的内存资源去创建栈帧,就会发生内存溢出。
例如如下代码:

public class Test {public static void m2(){m2();}public static void main(String[] args) {m2();}
}

上面这串代码的执行过程是:线程先执行main方法,同时会创建main方法的栈帧插入到该线程的线程栈中,当执行到m2()方法时,创建m2()方法的栈帧插入到该线程的线程栈中,执行到m2()方法里的m2()方法时,创建栈帧,插入到线程栈中,后面进行无脑创建栈帧、入栈。当创建一定数量的栈帧后,剩下的线程资源无法再创建新的栈帧
就会报StackOverflowError异常(堆栈溢出异常)(当前虚拟机栈不可以动态扩展)
异常截图:
在这里插入图片描述
如果Java虚拟机栈容量可以动态扩展,当栈扩展时无法申请到足够的内存会抛出OutOfMemoryError异常。

4、本地方法栈(线程私有)

本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务。
与虚拟机栈一样,本地方法栈也会在栈深度溢出或者栈扩展失败时分别抛出StackOverflowError和OutOfMemoryError异常。

5、程序计数器(线程私有)

程序计数器(Program Counter Register)也被称为 PC 寄存器,是一块较小的内存空间。它可以看作是当前线程所执行的字节码的行号指示器。程序计数器的作用是记录当前线程下一条要运行的指令,这样保证了线程在切换回来时能回到正确的位置继续开始执行。

6、易错点

  1. 根据方法区中的类型信息去创建对象时,该类的静态属性不会出现在新创建的对象中,原因是对于类来说,每个静态属性只存在一份,不属于该类的某个对象。所以当你去打印一个新创建的对象时,只会打印出非静态的属性的值
public class UserParam {public static int a=0;private String userName;private String nickName;private UserParam userParam;public int getTest() {return test;}public void setTest(int test) {this.test = test;}public String getUserName() {return userName;}public void setUserName(String userName) {this.userName = userName;}public String getNickName() {return nickName;}public void setNickName(String nickName) {this.nickName = nickName;}private int test;public UserParam getUserParam() {return userParam;}public void setUserParam(UserParam userParam) {this.userParam = userParam;}@Overridepublic String toString() {return "UserParam{" +"userName='" + userName + '\'' +", nickName='" + nickName + '\'' +", userParam=" + userParam +", test=" + test +'}';}
}
public static void main(String[] args) {UserParam userParam = new UserParam();UserParam.a=2;UserParam userParam1 = new UserParam();System.out.println(userParam);System.out.println(userParam1);
}

打印结果如下:

UserParam{userName='null', nickName='null', userParam=null, test=0}
UserParam{userName='null', nickName='null', userParam=null, test=0}
  1. 栈帧中的基本数据类型变量,只要赋值了,除非再次对其进行赋值,否则值不会改变。
public class Test {public static void main(String[] args) {UserParam userParam  = new UserParam();UserParam userParam1 = new UserParam();userParam1.setTest(userParam.getTest());System.out.println(userParam1);userParam.setTest(10);System.out.println(userParam1);}}

打印结果:

UserParam{userName='null', nickName='null', userParam=null, test=0}
UserParam{userName='null', nickName='null', userParam=null, test=0}
  1. 引用类型会根据引用数据的改变而改变。
public class Test {public static void main(String[] args) {UserParam userParam  = new UserParam();UserParam userParam1 = new UserParam();userParam1.setUserName("aaaaa");func(userParam,userParam1);System.out.println(userParam);}public static void func(UserParam userParam,UserParam  userParam1){userParam.setUserParam(userParam1);userParam1.setUserName("bbbbbbbb");}}

打印结果:

UserParam{userName='null', nickName='null', userParam=UserParam{userName='aaaaa', nickName='null', userParam=null, test=0}, test=0}
UserParam{userName='null', nickName='null', userParam=UserParam{userName='bbbbbbbb', nickName='null', userParam=null, test=0}, test=0}

可以看到,随着userParam1中userName的改变,userParam中的userParam也变了。原因是栈帧中引用类型变量存储的是堆中实例对象的地址,当实例对象改变,也意味着引用类型变量改变。

  1. 包装类型有拆装箱的过程,取值情况与基本数据类型一样
    在这里插入图片描述
public class Test {public static void main(String[] args) {UserParam userParam = new UserParam();userParam.setTest(10);UserParam userParam1 = new UserParam();userParam1.setTest(userParam.getTest());userParam.setTest(100);System.out.println(userParam1);}}

打印结果如下:

UserParam{userName='null', nickName='null', userParam=null, test=10}
  1. jdk1.8中,String存在于堆中的字符串常量中,也是个对象。(堆的唯一目的就是用来存放实例对象)
public class Test {public static void main(String[] args) {UserParam userParam  = new UserParam();userParam.setUserName("yhz");UserParam userParam1 = new UserParam();userParam1.setUserName(userParam.getUserName());userParam.setUserName("aaaa");System.out.println(userParam1);}}

打印结果:

UserParam{userName='yhz', nickName='null', userParam=null, test=null}

原因是:

  1. 创建完userParam对象,在给userParam设置userName为“yhz”时,会先去堆中的字符串常量池中创建“yhz”这个实例,然后将"yhz"实例的地址返回给userParam的userName。
  2. 创建完userParma1对象,在给userParma1设置userName为userParam.getUserName()时,userParam.getUserName()返回的userParam中userName保存的字符串常量池中"yhz"实例的地址,于是userParam1中userName指向字符串常量池中"yhz"实例(保存了字符串常量池中"yhz"实例的地址)。
  3. 当给userParam设置userName为"aaaa"时,会先去堆中的字符串常量池中创建“aaaa”这个实例,然后将"aaaa"实例的地址返回给userParam的userName,最后userParam的userName指向了"aaaa"然而userParam的userName还是指向"yhz"。

关于字符串常量池的一些内幕

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

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

相关文章

C语言第十一课--------操作符的使用与分类-------基本操作

作者前言 作者介绍: 作者id:老秦包你会, 简单介绍: 喜欢学习C语言和python等编程语言,是一位爱分享的博主,有兴趣的小可爱可以来互讨 个人主页::小小页面 gitee页面:秦大大 一个爱分享的小博主 欢迎小可爱们…

css 书写规范!其他人总结!

CSS书写顺序 1.位置属性(position, top, right, z-index, display, float等) 2.大小(width, height, padding, margin) 3.文字系列(font, line-height, letter-spacing, color- text-align等) 4.背景(background, border等) 5.其他(animation, transition等) CSS书写规范 使用…

MyBatis-Plus自定义sql注入器

文章目录 一、前言二、MyBatis-Plus自定义sql注入器功能实现2.1、编写自定义sql类2.2、将自定义sql添加到BaseMapper中2.3、编写自己的sql注入器 一、前言 在日常开发过程中,我们可能会发现 MyBatis-Plus 提供的那些自带的 sql 语句无法满足我们的开发需求&#xf…

web自动化测试进阶篇05 ——— 界面交互场景测试

😏作者简介:博主是一位测试管理者,同时也是一名对外企业兼职讲师。 📡主页地址:【Austin_zhai】 🙆目的与景愿:旨在于能帮助更多的测试行业人员提升软硬技能,分享行业相关最新信息。…

【Ajax】笔记-同源策略

同源策略(Same-Origin Policy),是浏览器的一种安全策略 同源(即url相同):协议、域名、端口号 必须完全相同。(请求是来自同一个服务) 跨域:违背了同源策略,即跨域。 ajax请求是遵循…

texshop mac中文版-TeXShop for Mac(Latex编辑预览工具)

texshop for mac是一款可以在苹果电脑MAC OS平台上使用的非常不错的Mac应用软件,texshop for mac是一个非常有用的工具,广泛使用在数学,计算机科学,物理学,经济学等领域的合作,这些程序的标准tetex分布特产…

flask中的session介绍

flask中的session介绍 在Flask中,session是一个用于存储特定用户会话数据的字典对象。它在不同请求之间保存数据。它通过在客户端设置一个签名的cookie,将所有的会话数据存储在客户端。以下是如何在Flask应用中使用session的基本步骤: 首先…

github Recv failure: Connection reset by peer

Recv failure: Connection reset by peer 背景处理ping一下github网页访问一下github项目git配置git ssh配置再次尝试拉取 疑惑点待研究参考 背景 晚上敲着代码准备提交,执行git pull,报错Recv failure: Connection reset by peer。看着这报错我陷入了沉…

【fly-iot飞凡物联】(12):EMQX 5.1使用docker 本地部署,接入到Actorcloud的数据库中,成功连接创建的设备,可以控制设备访问状态

目录 前言1,关于2,使用docker 进行部署3,配置API key 可以使用接口访问的4,设置客户端认证,连接PostgreSQL 数据连接5,使用客户端进行连接6,EMQX的API 接口地址7,总结 前言 本文的原…

应用层协议——http

文章目录 1. HTTP协议1.1 认识URL1.2 urlencode和urldecode1.3 HTTP协议格式1.3.1 HTTP请求1.3.2 HTTP响应1.3.3 外网测试1.3.4 添加html文件1.3.5 HTTP常见Header1.3.6 GET和POST 1.4 HTTP的状态码1.4.1 301和3021.4.2 代码实现 1.5 Cookie1.5.1 代码验证1.5.2 Cookiesession …

JetBrains 为测试自动化打造的强大 IDE-Aqua

QA 和测试工程对现代软件开发必不可少。 在 JetBrains,我们相信使用正确的工具对每项工作都很重要。 对我们来说,为自动化测试开发创建单独的工具是自然而然的事,因为这使我们能够满足多角色软件开发团队的需求。 我们很高兴能够推出 JetBra…

Transformer背景介绍

目录 Transformer的诞生Transformer的优势Transformer的市场 Transformer的诞生 论文地址 Transformer的优势 Transformer的市场

MHA高可用配置及故障切换

文章目录 MHA高可用配置及故障切换一. MySQL MHA1.什么是MHA2.MHA的组成2.1MHA Node (数据节点)2.2MHA Manager (管理节点) 3.MHA的特…

【观察】以超融合创新架构,加速企业应用现代化

我们知道,数字化转型的不断加速,核心就是应用的加速。在整个数字化体系中,软件应用是让一切发挥价值的落地路径。在应用发挥能力之前,企业需要进行大量软硬件准备以及应用开发工作;在应用开始发挥能力之,企…

认识什么是架构

目录 ​编辑 一、架构是什么 1.1 系统与子系统 1.1.1 系统 1.1.1.1 关联 1.1.1.2 规则 1.1.1.3 能力 1.1.2 子系统 1.2 模块与组件 1.2.1 模块 1.2.2 组件 1.3 框架与架构 1.3.1 框架 1.3.2 架构 1.3.2.1 架构定义 1.3.2.2 架构组成 1.3.2.2.1 要素 1.3.2.2.2 结构 1.3.2…

从零开始理解Linux中断架构(23)中断运行临界区和占先调度

Linux在内核中定义了6种运行临界区。 in_interrupt in_interrupt在驱动中使用频率最高的函数了,in_interrupt()就是指示Core是否正在中断处理中,包含了硬中断,软中断运行临界区。如果在中断处理中,则不能调用__do_softirq执行软中断处理。硬中断中不可调度不可中断,所有…

Linux vsftp三种模式的简单配置部署

环境:Debian 6.1.27-1kali1 (2023-05-12) vsftpd 安装 --查看是否当前系统是否已安装 apt list --installed | grep vsftpd 没有安装的话,就正常安装 apt-get update apt-get install vsftpd 一、匿名用户模式 分享一些不重要文件,任…

Android 通用带箭头提示窗

简介 自定义PopupWindow, 适用于提示类弹窗。 使用自定义Drawable设置带箭头的背景,测试控件和弹窗的尺寸,自动设置弹窗的显示位置,让箭头指向锚点控件的中间位置,且根据锚点控件在屏幕的位置,自动适配弹窗显示位置。…

C++——STL容器之list链表的讲解

目录 一.list的介绍 二.list类成员函数的讲解 2.2迭代器 三.添加删除数据: 3.1添加: 3.2删除数据 四.排序及去重函数: 错误案例如下: 方法如下: 一.list的介绍 list列表是序列容器,允许在序列内的任何…

css实现鼠标滑动左下角弹框带动画效果

代码 <div classNamekuang></div> css代码 .kuang {height: 500px;width: 400px;// background-color: #fff;position: absolute;z-index: 10;bottom: 0;transform: translateX(-390px)}.kuang:hover {animation: myanimation 3s linear 1;animation-fill-mode:f…