【JVM篇】Java内存区域与OOM

news2025/2/23 6:55:28

目录

1、概述

2、运行时数据区域 

3、程序计数器

4、Java虚拟机栈

5、本地方法栈

6、Java堆

7、方法区

8、运行时常量池

9、直接内存


1、概述

内存是非常重要的系统资源,是硬盘和 CPU 的中间仓库及桥梁,承载着操作系统和应用程序的实时运行。JVM 内存布局规定了 Java 在运行过程中内存申请、分配、管理的策略,保证了 JVM 的高效稳定运行。本文对JVM的数据区域及其对应的OOM原因进行了较为详细的分析。

2、运行时数据区域 

下图是JVM定义的各种运行时数据区域。 

可见,JVM运行时数据区域分为两类:

①线程私有:程序计数器、虚拟机栈、本地方法栈。

②线程共享:堆、方法区。

本文接下来将针对以上几个区域进行讲解。

3、程序计数器

---概括---:

程序计数器(Program Counter Register)是一块较小的内存空间,它可以看作用来记录当前线程执行的字节码的行号指示器。通俗点来说就是记录了当前线程执行的代码,字节码解释器便是通过改变程序计数器的值来选取下一条所需执行的代码。

这个描述是不是和我们计组学的寄存器很像?对!程序计数器就是在硬件层面通过寄存器来实现的!

---作用---:

在多线程运行的情况下,由于一个处理器(对于多核CPU来说是一个内核)只会执行一个线程,因此大多数情况是该线程执行完应有的时间片后会被挂起,因此等轮到它又被执行时,应当知道上一次执行的到哪了才可进行恢复。

从上述描述我们可以知道,程序计数器是用来记录“当前线程”当前所执行的代码,因此很好理解为什么是私有的。如果是公有的话,那所有线程共用一个程序计数器,可是程序计数器只能记录一行,那这行到底是记录哪个线程的?那其它线程怎么办?

其主要规则如下:

如果线程正在执行的是一个Java方法,那么程序计数器记录的是正在执行的虚拟机字节码的地址。如果线程正在执行的是一个本地方法,那么程序计数器则为空。

---是否会发生OOM及其原因---:

程序计数器是《Java虚拟机规范》中唯一一个没有规定任何OOM(OutOfMemoryError)情况的内存区域。

其实这也很好理解,因为程序计数器只记录了当前执行的指令,所以不会发生OOM。

4、Java虚拟机栈

---概括---:

Java虚拟机栈描述的是Java方法执行时,线程的内存模型。每个方法被执行时,都会在当前线程的虚拟机栈创建一个栈帧(Stack Frame)用于存放局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法的开始与结束可以理解成其在当前线程的虚拟机栈中对应的栈帧进行入栈和出栈的过程。

例如,假设有两个线程执行以下代码:

public class Test {
    public static void main(String[] args) {
        methodA();
    }
    public static void methodA(){
        System.out.println("方法A");
        methodB();
    }
    public static void methodB(){
        System.out.println("方法B");
        methodC();
    }
    public static void methodC(){
        System.out.println("方法C");
    }
}

其中,线程一执行到了方法C,而线程二执行到方法A。那么对应的虚拟机栈模型图如下:

从上图我们也可以看出,如果线程共用一个虚拟机栈,那么每个栈帧属于谁?而且规定仅有最顶上的栈帧才可运行,那这不就乱套了?可见,虚拟机栈也是线程私有的。

---是否会发生OOM及其原因---:

在《Java虚拟机规范》中,规定了Java虚拟机栈这个数据区域有两种错误:

①StackOverflowError:

这个大家根据英文意思来翻译就是:“栈溢出错误”。

大家观察上图可以发现,笔者画的虚拟机栈是有限深度的,因此当我们栈帧过多,当超过栈的上限时,便可能会溢出,发生这个错误。

通常这个错误大家平时写代码如果基本功不扎实很容易遇到,比如写递归时,忘记写退出条件,那么当递归到一定深度时,便会抛出这个异常。

还有一种情况便是一个栈帧“过长”,放入时直接超过了虚拟机栈的上线,也会发生StackOverflowError。

②OutOfMemoryError:

在Java虚拟机栈中,如果栈的内存空间允许动态扩展,那么当栈无法申请到更大的内存空间时,会抛出“OutOfMemoryError”异常。

在《Java虚拟机规范》中明确允许了Java虚拟机可以自由选择是否支持Java虚拟机栈动态扩展。我们平时常用的HotSpot虚拟机是不支持动态扩展的,因此如果使用的是HotSpot虚拟机是不会发生“OutOfMemoryError”,仅可能发生“StackOverflowError”。

5、本地方法栈

---概括---:

本地方法栈的作用和虚拟机栈的作用很相似,发生StackOverflowError和OutOfMemoryError的原因也一样,唯一的区别就是本地方法栈存放是执行Native(本地)方法。《Java虚拟机栈规范》并没有强制规定该数据区域使用的编程语言、数据结构、是否需要实现。因此有的虚拟机(如HotSpot)将其和虚拟机栈融为一体了。

由于本地方法栈和虚拟机栈内容几乎一样,因此我们这里不做过多讲解,大家参考上文即可。 

6、Java堆

---概括---:

《Java虚拟机规范》中对Java堆的描述是:“所有对象实例和数组都应分配在堆上”。

可见,Java堆肯定是线程共享的,唯一目的是用来存放对象实例和数组,也是虚拟机所管理的内存中最大的一块区域。同时这也是垃圾回收的主要区域,为什么说是主要区域,因为实际上“方法区”也是可以进行垃圾回收的,只不过条件比较苛刻而已。

---是否会发生OOM及其原因---:

稍微对这块区域有点了解的小伙伴肯定知道这块区域是会发生OOM的,因为我们上述说了,所有对象实例和数组都是分配在堆上,那么随着创建的对象实例和数组越来越多,肯定会发生OOM。例如我们执行下方代码:

public class Test {
    public static void main(String[] args) {
        List<int[]>list=new ArrayList<>();
        while(true)list.add(new int[100]);
    }
}

注意,随着现在我们硬件的发展,Java堆空间肯定也越来越大,同时Java堆是可以选择可扩展和固定的,目前我们主流的虚拟机为了防止轻易发生OOM,都选择了实现可扩展的Java堆,因此我们想通过上述代码测试Java堆的OOM,应当通过设置虚拟机参数-Xmx和-Xms,将这两个参数设置相同,表示固定大小,然后设置的稍微小一点即可。

当然,如果Java堆内存不足以完成实例的内存分配且无法扩展时,也会抛出OutOfMemoryError。

7、方法区

---概括---:

方法区可以理解成一种抽象的逻辑区域,主要是用于存储已被虚拟机加载的类的相关信息。

方法区也是同Java堆一样,是线程共享的区域。《Java虚拟机规范》中将方法区描述成了堆的一个逻辑部分,但是也给它取了个别名“非堆”,目的也是为了将其和堆区分开来。既然提到方法区,那我们就不得不提到方法区的两个实现“永久代”和“元空间”。

---永久代---:

在JDK6及之前,方法区的实现称为“永久代”,它仍是属于JVM管理内存的一部分。其主要用来存储已被JVM加载的类型信息、常量、静态变量、JIT编译器编译后的代码缓存等数据。

为了验证在JDK6之前方法区的实现是“永久代”,这里我们使用JDK6来编译、运行下述代码:

public class Test {
    public static void main(String[] args) {
        Set<String>strings=new HashSet<>();
        //JDK6及之前字符串常量池仍属于永久代
        int i=0;
        while (true){
            //intern()的作用是
            // 如果字符串常量池中没有这个常量则将此常量放入字符串常量池
            // 有的话则从字符串常量池中取
            //strings的作用是防止内存不足时,将没用到的常量进行回收
            strings.add(String.valueOf(i++).intern());
        }
    }
}

运行结果:

Exception in thread "main" java.lang.OutOfMemoryError:PermGen space

    at java.lang.String.intern(Native Method)

    at org.fenixsoft.oom.RuntimeConstantPoolOOM.main(RuntimeConstantPool.java: 20)

大家可以发现,这里抛出一个"OutOfMemoryError"异常,同时表明了出现异常的地方:“PermGen space” 翻译过来就是“永久代”。可见,在JDK6及之前方法区的实现是“永久代”,当我们不断创建新的字符串进运行时常量池,便会发生OOM异常。

---元空间---:

其实在JDK6开始呢,虚拟机开发人员就发现了如果永久代的一些弊端:比如会更容易发生OOM。因此在JDK6开始便逐渐有了“去永久代”的行为,直到JDK8,虚拟机开发者们便直接在“直接内存”来实现“方法区”,这个称为“元空间”。

其中,直接内存是指我们电脑的内存。同时,虚拟机开发者们将“静态变量”、“字符串常量池”等放入了“Java堆”中,而不是存在“方法区”中了。我们用上述相同代码,但是在JDK8环境下来验证这一点:

public class Test {
    public static void main(String[] args) {
        Set<String>strings=new HashSet<>();
        //JDK8及之后字符串常量池属于Java堆
        int i=0;
        while (true){
            //intern()的作用是
            // 如果常量池中没有这个常量则将此常量放入常量池
            // 有的话则从常量池中取
            //strings的作用是防止内存不足时,将没用到的常量进行回收
            strings.add(String.valueOf(i++).intern());
        }
    }
}

运行结果:

Exception in thread "main" java.lang.OutOfMemoryError:Java heap space

可见,此时字符串常量池是在Java堆中了。

虽然方法区在JDK8及之后完全用“元空间”来代替,使用的是“直接内存”,但是我们电脑内存肯定也是有限的,会有用完的一天。所以用元空间实现的方法区,虽然用的是“直接内存”,但是随着我们程序越搞越大,动态生成的类越来越多(比如spring运行过程中就是会动态生成类),当没有足够的内存进行存储时,也会抛出“OutOfMemoryError”异常。

8、运行时常量池

Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池表,用于存放编译期生成的各种字面量和符号引用。常量池表在类被加载后存放到方法区的运行时常量池中。

注意,看到这里大家可能会将“运行时常量池”和“字符串常量池”混为一谈,实际上字符串常量池是运行时常量池的一部分。

上面我们提到 “运行时常量池”是方法区的一部分,因此肯定也会受到方法区的内存限制,因此当申请不到需要的内存时,也会抛出“OutOfMemoryError”异常。

9、直接内存

直接内存是指我们电脑中的内存,不属于虚拟机运行时数据的一部分,也不是《Java虚拟机规范》中定义的数据区域。

Java从JDK1.4开始提供了 NIO类,它可以通过Native函数库直接分配堆外内存,即本机内存。虽然直接内存不受Java堆大小限制,但是我们电脑内存也是有限的,因此如果我们过度申请,总会用完的,届时也会抛出“OutOfMemoryError”异常。

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

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

相关文章

Windows Server 2025预览版安装配置

一、安装篇 1.2.目前最新版是 25379版本&#xff0c;需要注册微软账户&#xff0c;加入先期预览计划才可以下载。 下载完镜像后&#xff0c;制作U盘启动盘&#xff0c;安装。 注意&#xff1a;无论是rufs制作启动盘&#xff0c;还是在安装的时候&#xff0c;都要使用UEFI模式…

Spring boot开发微信小游戏后台-websocket服务

最近在做一个微信小游戏的后台&#xff0c;需要使用websocket与小游戏端建立连接&#xff0c;实时推送数据&#xff0c;小游戏后台是一个单体spring boot项目&#xff0c;管理玩家的一些基础信息和游戏配置信息&#xff0c;起初在这个单体项目中加入了websocket&#xff0c;可以…

spring boot+easyui粮油质量管控防伪溯源系统源码

基于物联网技术、RFID技术和RSA、PGP加密算法开发的粮油质量追溯系统 粮油安全关系千千万万消费者的健康问题。近年来&#xff0c;许多食品行业安全事故频频涌现&#xff0c;成为社会关注焦点。粮油生产加工质量管控防伪溯源系统为粮油提供从种植、生产、加工、销售等各环节的…

Linux下实现自己的printf函数

Linux下实现自己的printf函数 文章目录 Linux下实现自己的printf函数项目中的使用实现自己的printf函数 项目中的使用 /*********************************************************************** 函数名称&#xff1a; DebugPrint* 功能描述&#xff1a; 打印信息的总入口函…

Nodejs四、npm与包

零、文章目录 Nodejs四、npm与包 1、包 &#xff08;1&#xff09;包是什么 Node.js 中的第三方模块又叫做包。就像电脑和计算机指的是相同的东西&#xff0c;第三方模块和包指的是同一个概念&#xff0c;只不过叫法不同。 &#xff08;2&#xff09;包的来源 不同于 Nod…

Linux SSH PublicKey 登录

前言 ssh 远程登录密码认证的方式有 Password、Keyboard Interactive 和 Public Key 三种主要方式。 前面两种方式就是密码认证&#xff0c;含义都是一样大同小异。第三种是登录方式最安全的一种&#xff0c;也是我们常用的云服务器默认使用的一种方式。 本文就如何配置并使用…

torchvision.ops.nms实现NMS

nms原理&#xff1a; 当目标检测模型对一个目标有多个检测框时&#xff0c;需要滤掉多余的框&#xff0c;留下最接近真实目标的框。 步骤是这样的&#xff1a; 1.先把目标框初筛一波&#xff0c;比如设阈值为0.25, 把预测概率 < 0.25的目标框滤掉。 2.把 每个类别的 目标框 …

DEVONthink 3:Mac文档管理工具,知识管理app

DEVONthink Pro是一款功能强大的文档管理软件&#xff0c;它可以帮助用户高效地组织、管理和查找各种类型的文件和信息。 下面是DEVONthink Pro的主要特点介绍&#xff1a; 多功能性&#xff1a;DEVONthink Pro支持多种文件类型和数据源&#xff0c;并提供全面的搜索、分类、过…

在 ZBrush、Substance 3D Painter 和 UE5 中创作警探角色(P1)

小伙伴们大家好&#xff0c;今天瑞云渲染小编给大家分享的是自由CG艺术家Jean Zoudi创建《极乐迪斯科》的警探角色的项目花絮&#xff0c;会解释身体和服装的建模方式&#xff0c;分享角色发型和面部毛发背后的工作流程&#xff0c;也会详细介绍渲染过程。 介绍 大家好&#…

性能测试怎么做?性能测试策略配套适用场景,打通性能测试...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、常见的测试策略…

直流对数放大器

Logarithmic Amplifiers 对数放大器的应用场合 在雷达和一些其他测距的场合&#xff0c;sensor输出的信号的动态范围比较宽&#xff0c;也就是要求sensor输出的弱信号时有比较大的放大倍数&#xff0c;强的信号有较小的放大倍数&#xff0c;以保证sensor输出的信号经过放大器后…

可移动硬盘无媒体是什么意思?移动硬盘显示无媒体数据如何恢复

案例分享&#xff1a;【最近我遇到了一个麻烦&#xff0c;我的移动硬盘突然显示“无媒体”。我不知道发生了什么&#xff0c;我很担心我的硬盘中存储的大量重要数据是否还能恢复。我该怎么解决移动硬盘显示无媒体问题呢&#xff0c;求大神帮帮我吧&#xff01;&#xff01;&…

浏览器是如何实现生成HTTP消息的

我们经常会使用浏览器访问各种网站&#xff0c;获取各种信息&#xff0c;帮助解决工作生活中的问题。那你知道&#xff0c;浏览器是怎么帮助我们实现对web服务器的访问&#xff0c;并返回给我们想要的信息吗&#xff1f; 1. 浏览器生成HTTP消息 我们平时使用的浏览器有很多种&…

【强烈推荐】 十多款2023年必备国内外王炸级AI工具 (免费 精品 好用) 让你秒变神一样的装逼佬感受10倍生产力 (6) AI学习

&#x1f680; 个人主页 极客小俊 ✍&#x1f3fb; 作者简介&#xff1a;web开发者、设计师、技术分享博主 &#x1f40b; 希望大家多多支持一下, 我们一起进步&#xff01;&#x1f604; &#x1f3c5; 如果文章对你有帮助的话&#xff0c;欢迎评论 &#x1f4ac;点赞&#x1…

【LLMs系列】90%chatgpt性能的小羊驼Vicuna模型学习与实战

一、前言 UC伯克利学者联手CMU、斯坦福等&#xff0c;再次推出一个全新模型70亿/130亿参数的Vicuna&#xff0c;俗称「小羊驼」&#xff08;骆马&#xff09;。小羊驼号称能达到GPT-4的90%性能 github 地址: GitHub - lm-sys/FastChat: An open platform for training, servi…

ChatGPT爆火网络背后的故事?

文章目录 前言一、ChatGPT的诞生背景二、ChatGPT的技术原理三、ChatGPT的推广策略四、ChatGPT的未来展望五、橙子送书第2期 前言 ChatGPT是一款基于人工智能技术的聊天机器人&#xff0c;它的出现引起了广泛的关注和热议。在短短的时间内&#xff0c;ChatGPT就成为了全球范围内…

实测|飞凌嵌入式OK3588-C开发板4G模组的使用与测试

本篇试用报告由发烧友 ouxiaolong提供&#xff0c;感谢ouxiaolong的支持。飞凌嵌入式会持续开展开发板有奖试用活动&#xff0c;更有京东E卡等着你&#xff01;欢迎大家的持续关注。 飞凌嵌入式OK3588-C开发板是一款性能强劲的旗舰产品&#xff0c;采用核心板底板的分体式设计…

linuxOPS系统服务_Linux下用户管理

用户概念以及基本作用 **用户&#xff1a;**指的是Linux操作系统中用于管理系统或者服务的人 一问&#xff1a;管理系统到底在管理什么&#xff1f; 答&#xff1a;Linux下一切皆文件&#xff0c;所以用户管理的是相应的文件 二问&#xff1a;如何管理文件呢&#xff1f; …

JDK、JRE、JVM三者的区别

JDK&#xff08;Java Development Kit&#xff09;&#xff1a;Java开发工具包 JRE&#xff08;Java Runtime Environment&#xff09;&#xff1a;Java运行环境 JVM&#xff08;Java Virtual Mechinal&#xff09;&#xff1a;Java虚拟机 &#xff08;1&#xff09;JDK和JRE 是…

Python海龟画图 几种基本图形

注&#xff1a;本文主要根据绘制步骤进行区分&#xff0c;实际使用时应当调节参数以绘制需要的图形。文中的步骤均为循环进行&#xff0c;循环50到100次&#xff0c;具体次数见代码示例。 1.前进小角度旋转 绘制效果如图&#xff0c;如果旋转角度为360的因数则绘制出多边形。 …