【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;
    }

    @Override
    public 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.mfbz.cn/a/51061.html

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

相关文章

使用克拉默法则进行三点定圆(二维)

目录 1.二维圆2.python代码3.计算结果 本文由CSDN点云侠原创,爬虫网站请自重。 1.二维圆 已知不共线的三个点,设其坐标为 ( x 1 , y 1 ) (x_1,y_1) (x1​,y1​)、 ( x 2 , y 2 ) (x_2,y_2) (x2​,y2​)、 ( x 3 , y 3 ) (x_3,y_3) (x3​,y3​)&#xf…

Ubuntu-文件和目录相关命令一

🔮linux的文件系统结构 ⛳目录结构及目录路径 🧩文件系统层次结构标准FHS Filesystem Hierarchy Standard(文件系统层次结构标准) Linux是开源的软件,各Linux发行机构都可以按照自己的需求对文件系统进行裁剪,所以众多…

Python - OpenCV实现摄像头人脸识别(亲测版)

要使用Python 3和OpenCV进行摄像头人脸识别,您可以按照以下步骤进行操作: 0.安装OpenCV软件 去官网直接下载安装即可,如果是C使用OpenCV,需要使用编译源码并配置环境变量。 1.安装OpenCV库 在命令行中输入以下命令: pip inst…

渗透测试基础知识(1)

渗透基础知识一 一、Web架构1、了解Web2、Web技术架构3、Web客户端技术4、Web服务端组成5、动态网站工作过程6、后端存储 二、HTTP协议1、HTTP协议解析2、HTTP协议3、http1.1与http2.0的区别4、HTTP协议 三、HTTP请求1、发起HTTP请求2、HTTP响应与请求-HTTP请求3、HTTP响应与请…

具有电动驱动的四足机器人模型研究(SimulinkMatlab代码)

💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…

[NLP]LLM高效微调(PEFT)--LoRA

LoRA 背景 神经网络包含很多全连接层,其借助于矩阵乘法得以实现,然而,很多全连接层的权重矩阵都是满秩的。当针对特定任务进行微调后,模型中权重矩阵其实具有很低的本征秩(intrinsic rank),因…

ajax axios json

目录 一、ajax概述 1. 概念 2. 实现方式 (1)原生的JS实现方式(了解) (2) JQeury实现方式 二、axios 介绍 三、axios使用 1. axios 发送get/post请求 2. axios验证用户名称是否存在 四、json 1. …

2023牛客暑期多校-J-Qu‘est-ce Que C‘est?(DP)

题意: 给定长度为n的数列,要求每个数都在的范围,且任意长度大于等于2的区间和都大于等于0,问方案数。。 思路: 首先要看出是dp题,用来表示遍历到第i位且后缀和最小为x的可行方案数(此时的后缀可以只有最…

[golang gin框架] 42.Gin商城项目-微服务实战之后台Rbac微服务角色增删改查微服务

一.重构后台Rbac用户登录微服务功能 上一节讲解了后台Rbac微服务用户登录功能以及Gorm数据库配置单独抽离,Consul配置单独抽离,这一节讲解后台Rbac微服务角色增删改查微服务功能,Rbac微服务角色增删改查微服务和后台Rbac用户登录微服务是属于…

苍穹外卖day09——历史订单模块(用户端)+订单管理模块(管理端)

查询历史订单——需求分析与设计 产品原型 业务规则 分页查询历史订单 可以根据订单状态查询 展示订单数据时,需要展示的数据包括:下单时间、订单状态、订单金额、订单明细(商品名称、图片) 接口设计 查询历史订单——代码开…

AI聊天GPT三步上篮!

1、是什么? CHATGPT是OpenAI开发的基于GPT(Generative Pre-trained Transformer)架构的聊天型人工智能模型。也就是你问它答,根据网络抓去训练 2、怎么用? 清晰表达自己诉求,因为它就是一个AI助手&#…

Java书签 #解锁MyBatis的4种批量插入方式及ID返回姿势

1. 今日书签 项目开发中,我们经常会用到单条插入和批量插入。但是实际情况可能是,项目初期由于种种原因,在业务各处直接使用单条插入SQL进行开发(未开启批处理),在后面的迭代中,系统性能问题渐…

无涯教程-jQuery - Ajax Tutorial函数

AJAX是用于创建交互式Web应用程序的Web开发技术。如果您了解JavaScript,HTML,CSS和XML,则只需花费一个小时即可开始使用AJAX。 为什么要学习Ajax? AJAX代表 A 同步 Ja vaScript和 X ML。 AJAX是一项新技术,可借助XML,HTML,CSS和Java Script创建更好,更快,更具交互性的Web应用…

解决Font family [‘sans-serif’] not found问题

序言 以下测试环境都是在 anaconda3 虚拟环境下执行。 激活虚拟环境 conda activate test_python_env 或 source activate test_python_env工具: WinSCP Visual Studio Code 这里笔者使用 WinSCP 工具连接,编辑工具是 Visual Studio Code 一、字体…

基于fpga_EP4CE6F17C8实现的呼吸灯

文章目录 前言实验手册(EP4CE6F17C8)一、实验目的二、实验原理理论原理 三、系统架构设计四、模块说明1.模块端口信号列表2.状态转移图3.时序图 五、仿真波形图六、引脚分配七、代码实现八、仿真代码九、板级验证效果 …

【论文阅读】Feature Inference Attack on Shapley Values

摘要 研究背景 近年来,解释性机器学习逐渐成为一个热门的研究领域。解释性机器学习可以帮助我们理解机器学习模型是如何进行预测的,它可以提高模型的可信度和可解释性。Shapley值是一种解释机器学习模型预测结果的方法,它可以计算每个特征对…

视频标注是什么?和图像数据标注的区别?

视频数据标注是对视频剪辑进行标注的过程。进行标注后的视频数据将作为训练数据集用于训练深度学习和机器学习模型。这些预先训练的神经网络之后会被用于计算机视觉领域。 自动化视频标注对训练AI模型有哪些优势 与图像数据标注类似,视频标注是教计算机识别对象…

springboot整合myabtis+mysql

一、pom.xml <!--mysql驱动包--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><!--springboot与JDBC整合包--><dependency><groupId>org.springframework.b…

hcip——路由策略

要求&#xff1a; 基础配置 AR1 [R1]int g 0/0/0 [R1-GigabitEthernet0/0/0]ip add 12.0.0.1 24[R1-GigabitEthernet0/0/0]int g 0/0/1 [R1-GigabitEthernet0/0/1]ip add 14.0.0.1 24[R1]int loop0 [R1-LoopBack0]ip add 1.1.1.1 24[R1]rip 1 [R1-rip-1]vers 2 [R1-rip-1]net…

基于扩展(EKF)和无迹卡尔曼滤波(UKF)的电力系统动态状态估计(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…
最新文章