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

news2024/11/24 16:49:48

文章目录

  • 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.coloradmin.cn/o/785540.html

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

相关文章

Docker 镜像构建 搭建分布式LNMP论坛 实践

地址规划 nginx 172.18.0.10 mysql 172.18.0.20 php 172.18.0.30 宿主机准备 拉取镜像,下面以此镜像为基础 docker pull centos:7 创建自定义网段以便指定 IP 不变动 docker network create --subnet172.18.0.0/16 --opt "com.docker.network.bridge.na…

【计算机网络】计算机网络基础知识总结(秋招篇)

文章目录 前言计算机网络笔记TCP和UDP分别是什么 有什么区别基于TCP UDP这两个协议的上层协议有哪些?TCP和UDP分别在哪些领域被用的多?TCP实现可靠性传输用了哪些技术?(TCP如何实现可靠性传输)讲一下超时重传和超时定时…

T3/A40i支持Linux-5.10新内核啦,Docker、Qt、Python统统升级!

自2021年创龙科技推出全志国产化率100%的T3/A40i工业核心板后,不到两年时间已超过800家工业客户选择创龙科技T3/A40i平台。随着客户产品的不断升级与迭代,部分“能源电力”、“工业自动化”行业客户对T3/A40i的Linux版本提出了更高要求,主要涉…

Jmeter查看结果树之查看响应的13种详解方法

Jmeter查看结果树查看响应有哪几种方法,可通过左侧面板底部的下拉框选择: 01 Text 查看结果树中请求的默认格式为Text,显示取样器结果、请求、响应数据3个部分内容。 取样器结果: 默认Raw展示,可以切换为Parsed视图&#xff0c…

用i18next使你的应用国际化-Next.js(App router)

安装插件 npm install i18next react-i18next i18next-resources-to-backend1. 目录结构 . └── app└── [lng]├── second-page| └── page.js├── layout.js└── page.jsapp/[lng]/page.js文件: import Link from next/linkexport default funct…

新增WebDB和ChatGPT组件,支持对ChatGPT资产进行纳管,JumpServer堡垒机v3.5.0发布

2023年7月24日,JumpServer开源堡垒机正式发布v3.5.0版本。在这一版本中,新生代数据库连接组件——问题终结者Chen强势来袭,替代原有的OmniDB组件,在兼容旧版本的同时,解决了旧组件性能不足的问题,为用户提供…

Matlab进阶绘图第23期—密度散点图

密度散点图本质上是一种特征渲染的散点图,其颜色表示某一点所在区域的密度信息。 除了作图,密度散点图绘制的关键还在于密度的计算。 当然,不管是作图还是密度的计算,这些在《Matlab论文插图绘制模板》和《Matlab点云处理及可视…

什么是PostgreSQL?简要介绍其主要特点和用途

PostgreSQL是一种开源的关系型数据库管理系统(DBMS),它是最强大和广泛使用的开源数据库之一。PostgreSQL的名称起源于其前身,称为"Ingres"项目,后来被命名为Postgres,而PostgreSQL则是它的进一步…

tinkerCAD案例:7.Skull Button 骷髅纽扣

tinkerCAD案例:7.Skull Button 骷髅纽扣 In this lesson you will learn to make a skull shaped button. Let’s get started! 在本课中,您将学习制作一个骷髅形按钮。让我们开始吧! 说明 Drag a Cylinder shape to the workplane. 将“圆柱…

ABAP中截取字符串中间位数常规方法

问题:截取字符串“SNTY2TI 071082241AF”中07108,常规字符串截取方法。 这里直接上代码: REPORT zsy_zm_test19. DATA:lv_datum TYPE sy-datum VALUE 20230428,lv_datum2 TYPE sy-datum VALUE 20220522. DATA:lv_num TYPE i. DATA:lv_strin…

Python基于PyTorch实现卷积神经网络分类模型(CNN分类算法)项目实战

说明:这是一个机器学习实战项目(附带数据代码文档视频讲解),如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 卷积神经网络,简称为卷积网络,与普通神经网络的区别是它的卷积层内的神经元只覆…

三个月诞生79个基础大模型,企业选用大模型需要注意些什么?

自从ChatGPT横空出世,各类大模型层出不穷,竞争也日渐激烈,可谓“乱花渐欲迷人眼”。 随着大公司的入场,无疑给创业公司带来了降维打击,创业公司随时可能倒掉,造成项目烂尾。 我也一直在关注大模型领域的最…

【深度学习】yolov 图片训练的时候的遇到的warning: corrupt JPEG restored and saved

报错原因 是图片在dataset.py 走验证时报的错误。 if im.format.lower() in (jpg, jpeg):with open(im_file, rb) as f:f.seek(-2, 2)if f.read() ! b\xff\xd9: # corrupt JPEGImageOps.exif_transpose(Image.open(im_file)).save(im_file, JPEG, subsampling0, quality100)m…

1-8 Burpsuite 漏洞扫描介绍

Burpsuite Scanner介绍 Burp Scanner的功能主要是用来自动检测web系统的各种漏洞,我们可以使用Burp Scanner代替我们手工去对系统进行普通漏洞类型的渗透测试,从而能使得我们把更多的精力放在那些必须要人工去验证的漏洞上。 进一步解放我们的生产力&a…

Spring Boot 中的日志

一、日志有什么用? 日志是程序的重要组成部分,想象一下,如果程序报错了,不让你打开控制台看日志,那么你能找到报错的原因吗? 答案是否定的,写程序不是买彩票,不能完全靠猜&#xf…

腾讯校园招聘技术类编程题汇总

题解&#xff1a;并查集&#xff08;模板&#xff09; #include <iostream> #include<map> using namespace std; int father[2000006]; int rank1[1000005]; void init(int n){for(int i1;i<1e5;i){father[i]i;rank1[i]1;} } int find(int x){if(father[x]x){…

数据可视化 - 动态柱状图

基础柱状图 通过Bar构建基础柱状图 from pyecharts.charts import Bar from pyecharts.options import LabelOpts # 使用Bar构建基础柱状图 bar Bar() # 添加X轴 bar.add_xaxis(["中国", "美国", "英国"]) # 添加Y轴 # 设置数值标签在右侧 b…

上门家政系统开发|上门预约家政小程序定制系统

随着人们生活水平的提高&#xff0c;对于家政服务的需求也越来越高。上门家政小程序的开发为家政服务商家提供了一个全新的经营和服务渠道。本文将介绍上门家政小程序适合的商家以及其优势。   1. 家政公司   家政公司是最直接受益于上门家政小程序开发的商家。通过开发家政…

AMEYA360代理线:ROHM开发出EcoGaN™减少服务器和AC适配器等的损耗和体积!

全球知名半导体制造商ROHM&#xff08;总部位于日本京都市&#xff09;面向数据服务器等工业设备和AC适配器等消费电子设备的一次侧电源*1&#xff0c;开发出集650V GaN HEMT*2和栅极驱动用驱动器等于一体的Power Stage IC“BM3G0xxMUV-LB”&#xff08;BM3G015MUV-LB、BM3G007…

【JavaEE初阶】HTTP协议

文章目录 1. HTTP概述和fiddler的使用1.1 HTTP是什么1.2 抓包工具fiddler的使用1.2.1 注意事项1.2.2 fiddler的使用 2. HTTP协议格式2.1 HTTP请求格式2.1.1 基本格式2.1.2 认识URL2.1.3 方法 2.2 请求报头关键字段2.3 HTTP响应格式2.3.1 基本格式2.3.2状态码 1. HTTP概述和fidd…