Java内存区域与内存溢出异常(自动内存管理)

        序言:Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人却想出来。

1.1概述

        对于从事C、C++程序开发的开发人员来说,在内存管理领域,他们既是拥有最高权力的“皇帝”,又是从事最基础工作的劳动人民——既拥有每一个对象的“所有权”,又担负着每一个对象生命从开始到终结的维护责任。对于Java程序员来说,在虚拟机自动内存管理机制的帮助下,不再需要为每一个new操作去写配对的delete/free代码,不容易出现内存泄漏和内存溢出问题,看起来由虚拟机管理内存一切都很美好。不过,也正是因为Java程序员把控制内存的权力交给了Java虚拟机,一旦出现内存泄漏和溢出方面的问题,如果不了解虚拟机是怎样使用内存的,那排查错误、修正问题将会成为一项异常艰难的工作。

1.2运行时数据区域

       Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而一直存在,有些区域则是依赖用户线程的启动和结束而建立和销毁。根据《Java虚拟机规范》的规定,Java虚拟机所管理的内存将会包括以下几个运行时数据区域,如图所示。

1.2.1程序计数器

       程序计数器(Program Counter Register)是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。在Java虚拟机的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,它是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。由于Java虚拟机的多线程是通过线程轮流切换、分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)都只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是本地(Native)方法,这个计数器值则应为空(Undefined)。此内存区域是唯一一个在《Java虚拟机规范》中没有规定任何OutOfMemoryError情况的区域。 

1.2.2 Java虚拟机栈

        与程序计数器一样,Java虚拟机栈(Java Virtual Machine Stack)也是线程私有的它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的线程内存模型:每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态连接、方法出口等信息。每一个方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。经常有人把Java内存区域笼统地划分为堆内存(Heap)和栈内存(Stack),这种划分方式直接继承自传统的C、C++程序的内存布局结构,在Java语言里就显得有些粗糙了,实际的内存区域划分要比这更复杂。不过这种划分方式的流行也间接说明了程序员最关注的、与对象内存分配关系最密切的区域是“堆”和“栈”两块。其中,“堆”在稍后专门讲述,而“栈”通常就是指这里讲的虚拟机栈,或者更多的情况下只是指虚拟机栈中局部变量表部分。

        局部变量表存放了编译期可知的各种Java虚拟机基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,它并不等同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或者其他与此对象相关的位置)和returnAddress类型(指向了一条字节码指令的地址)。这些数据类型在局部变量表中的存储空间以局部变量槽(Slot)来表示,其中64位长度的long和double类型的数据会占用两个变量槽,其余的数据类型只占用一个。局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在栈帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。请读者注意,这里说的“大小”是指变量槽的数量,虚拟机真正使用多大的内存空间(譬如按照1个变量槽占用32个比特、64个比特,或者更多)来实现一个变量槽,这是完全由具体的虚拟机实现自行决定的事情。

        在《Java虚拟机规范》中,对这个内存区域规定了两类异常状况:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;如果Java虚拟机栈容量可以动态扩展,当栈扩展时无法申请到足够的内存会抛出OutOfMemoryError异常。

1.2.3本地方法栈

        本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别只是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的本地(Native)方法服务。《Java虚拟机规范》对本地方法栈中方法使用的语言、使用方式与数据结构并没有任何强制规定,因此具体的虚拟机可以根据需要自由实现它,甚至有的Java虚拟机(譬如Hot-Spot虚拟机)直接就把本地方法栈和虚拟机栈合二为一。与虚拟机栈一样,本地方法栈也会在栈深度溢出或者栈扩展失败时分别抛出StackOverflowError和OutOfMemoryError异常。

1.2.4Java堆

        对于Java应用程序来说,Java堆(Java Heap)是虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,Java世界里“几乎”所有的对象实例都在这里分配内存。在《Java虚拟机规范》中对Java堆的描述是:“所有的对象实例以及数组都应当在堆上分配”,而这里笔者写的“几乎”是指从实现角度来看,随着Java语言的发展,现在已经能看到些许迹象表明日后可能出现值类型的支持,即使只考虑现在,由于即时编译技术的进步,尤其是逃逸分析技术的日渐强大,栈上分配、标量替换优化手段已经导致一些微妙的变化悄然发生,所以说Java对象实例都分配在堆上也渐渐变得不是那么绝对了。

        Java堆是垃圾收集器管理的内存区域,因此一些资料中它也被称作“GC堆”(Garbage Collected Heap,幸好国内没翻译成“垃圾堆”)。从回收内存的角度看,由于现代垃圾收集器大部分都是基于分代收集理论设计的,所以Java堆中经常会出现“新生代”“老年代”“永久代”“Eden空间”“From Survivor空间”“To Survivor空间”等名词,这些概念在本书后续章节中还会反复登场亮相,在这里笔者想先说明的是这些区域划分仅仅是一部分垃圾收集器的共同特性或者说设计风格而已,而非某个Java虚拟机具体实现的固有内存布局,更不是《Java虚拟机规范》里对Java堆的进一步细致划分。不少资料上经常写着类似于“Java虚拟机的堆内存分为新生代、老年代、永久代、Eden、Survivor……”这样的内容。在十年之前(以G1收集器的出现为分界),作为业界绝对主流的HotSpot虚拟机,它内部的垃圾收集器全部都基于“经典分代”[插图]来设计,需要新生代、老年代收集器搭配才能工作,在这种背景下,上述说法还算是不会产生太大歧义。但是到了今天,垃圾收集器技术与十年前已不可同日而语,HotSpot里面也出现了不采用分代设计的新垃圾收集器,再按照上面的提法就有很多需要商榷的地方了。

        如果从分配内存的角度看,所有线程共享的Java堆中可以划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB),以提升对象分配时的效率。不过无论从什么角度,无论如何划分,都不会改变Java堆中存储内容的共性,无论是哪个区域,存储的都只能是对象的实例,将Java堆细分的目的只是为了更好地回收内存,或者更快地分配内存。在本章中,我们仅仅针对内存区域的作用进行讨论,Java堆中的上述各个区域的分配、回收等细节将会是下一篇的主题。

        根据《Java虚拟机规范》的规定,Java堆可以处于物理上不连续的内存空间中,但在逻辑上它应该被视为连续的,这点就像我们用磁盘空间去存储文件一样,并不要求每个文件都连续存放。但对于大对象(典型的如数组对象),多数虚拟机实现出于实现简单、存储高效的考虑,很可能会要求连续的内存空间。Java堆既可以被实现成固定大小的,也可以是可扩展的,不过当前主流的Java虚拟机都是按照可扩展来实现的(通过参数-Xmx和-Xms设定)。如果在Java堆中没有内存完成实例分配,并且堆也无法再扩展时,Java虚拟机将会抛出OutOfMemoryError异常。

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

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

相关文章

在FPGA程序中Handshake(握手)和Register(寄存器)区别

在FPGA程序中,Handshake(握手)和Register(寄存器)是两种不同的通信和数据传输机制。它们各有特点和适用场景。以下是它们的区别和应用场景的详细解释: Register(寄存器) 特点&#…

SQLServer用户们,你们摊上大事了!

最近一段时间,我们经常会收到了许多用户的咨询,问我们何时能纳管SQLServer?耐不住小伙伴们的猛烈催促及热切期待,本不想纳管SQLServer的研发团队也抓紧将这项需求提上日程。并在DBdoctor v3.2.2版本中成功实现了对SQLServer的纳管…

班级录取查询系统如何制作

在教育的长河中,我们每位老师都曾面临过这样一个问题:如何高效、准确地完成班级录取查询的任务?记得在以往,每当新学期伊始,我们不得不手忙脚乱地整理学生名单,然后逐一通知他们所在的班级。这个过程不仅耗…

《mysql篇》--索引事务

索引 索引的介绍 索引是帮助MySQL高效获取数据的数据结构,是一种特殊的文件,包含着对数据表里所有记录的引用指针,因为索引本身也比较大,所以索引一般是存储在磁盘上的,索引的种类有很多,不过如果没有特殊…

[ios-h5]在ios系统浏览器中输入框得到焦点时页面自动放大

问题&#xff1a; 在ios系统浏览器中输入框得到焦点时页面自动放大。 解决&#xff1a; 添加meta标签。 <meta name"apple-mobile-web-app-capable" content"yes" /> <meta name"viewport" content"widthdevice-width, initial-…

隐式类型转换 算术转换

目录 整型提升 间接证明整型提升的代码案例 算术转换 整型提升 何为整型提升&#xff1a; C语言的整型算术运算总是至少以缺省&#xff08;默认&#xff09;整型类型的精度来进行的 为了获得这个精度&#xff0c;表达式中的字符类型和短整型操作数在使用之前被转换为普通整…

基于SpringBoot实现轻量级的动态定时任务调度

在使用SpringBoot框架进行开发时&#xff0c;一般都是通过Scheduled注解进行定时任务的开发&#xff1a; Component public class TestTask {Scheduled(cron"0/5 * * * * ? ") //每5秒执行一次public void execute(){SimpleDateFormat df new SimpleDateFormat(…

解决 NullReferenceException: Object reference not set to an instance of an object

在 Unity 中 利用 URDF Importer import UR5e_gripper 的 URDF file 时出现错误&#xff1a; NullReferenceException: Object reference not set to an instance of an object。 理论上是有个Object 是 Null&#xff0c;当我再次检查URDF后仍觉得路径没有写错。 于是我 把Mesh…

软件测试面试200问(含答案+文档)

Part1 1、你的测试职业发展是什么&#xff1f; 测试经验越多&#xff0c;测试能力越高。所以我的职业发展是需要时间积累的&#xff0c;一步步向着高级测试工程师奔去。而且我也有初步的职业规划&#xff0c;前3年积累测试经验&#xff0c;按如何做好测试工程师的要点去要求自…

spring的bean注册

bean注册 第三方jar包的类想添加到ioc中&#xff0c;加不了Component该怎么办呢。 可以使用Bean和Import引入jar包&#xff0c;可以使用maven安装到本地仓库。 修改bean的名字&#xff1a;Bean("aaa")使用ioc的已经存在的bean对象&#xff0c;如Country&#xff1a;p…

自定义@AnonymousAccess注解

一.目的&#xff1a; 自定义AnonymousAccess注解&#xff0c;可以直接在controller上添加该注解使请求绕过权限验证进行匿名访问&#xff0c;便于快速调用调试以及部分不需要进行安全验证的接口。而不是每次都需要去SecurityConfig文件中进行修改。 二.流程&#xff1a; 三.实…

通用后台管理(二)——项目搭建

目录 前言 一、安装vue-cli依赖 1、使用yarn下载vue-cli 2、使用npm下载 3、检查一下是否下载成功 二、创建项目 1、创建项目&#xff0c;my-app是项目名称 2、 这里选择vue 2&#xff0c;蓝色表示选中的。 3、启动项目 三、下载项目依赖 四、配置项目 1、修改esli…

VS2019_新建窗体应用文件SOP

文章目录 1. 打开VS2. 创建新项目3. 选择Windows窗体应用4. 起名&#xff0c;选位置&#xff0c;框架版本选择4.5及之后&#xff0c;这里选的4.85. 新建完成 1. 打开VS 2. 创建新项目 3. 选择Windows窗体应用 注意要选.NET的 4. 起名&#xff0c;选位置&#xff0c;框架版本…

原来Kimi不是不作为,而是在准备大招!

月之暗面Kimi 作为一款我每天都在使用的AI智能助手&#xff0c;是真正的帮助我解决了不少工作及日常创作的一些事情。 它的表现能力也是毋庸置疑&#xff0c;不论是业内还是普通人&#xff0c;10个人当中可能就有9个人在使用Kimi。 而昨天&#xff08;7月8日&#xff09;Kimi…

Civil3D 2025:工程设计的智慧之友

初识Civil3D 2025 当我第一次打开Civil3D 2025时&#xff0c;仿佛置身于湘西的山水之间&#xff0c;那种熟悉而又新奇的感觉扑面而来。Civil3D 2025不仅仅是一款软件&#xff0c;它更像是一位老友&#xff0c;带着温暖和智慧&#xff0c;陪伴我走过每一个设计的日夜。 功能丰…

侯捷C++面向对象高级编程(上)-9-扩展补充:类模板、函数模板及其他

1.static 2.把 ctors 放在 private 区 3.cout 4.class template、类模板 5.function template、函数模板 6.namespace

DDR3 SO-DIMM 内存条硬件总结(二)

上一节我们大概了解了下DDR3的大概使用情况&#xff0c;下来我们再顺藤摸瓜再深入学习下ddr3的使用&#xff0c;我们从下面这张内存条开始&#xff1a; 我们看到第二行2GB 1RX8 PC3-10600S-9-11-B2 具体含义上一节已经讲过了&#xff0c;我们深挖下1R*8&#xff0c;他的含义是…

资料分析笔记整理

提升技巧多做题、少动笔、多分析 资料分析认识 国考一般20题(24~28分钟) 统计材料的类型包括单纯的文字、表格、图形以及由这些元素组成的复合类型材料 文字性材料:(30~60秒) 多段落型文字材料(时间、关键词、结构) 孤立段落文字材料(时间、关键词、标点[。;]) 表…

Java语言程序设计——篇二(2)

Java语言基础 运算符与表达式运算符1、算术运算符2、关系运算符3、逻辑运算符&#xff08; &&、||、 !、&、| 、^&#xff09;4、位运算符&#xff08; >>、<<、>>>、&、|、^、~&#xff09;5、赋值运算符6、条件运算符7、字符串运算符8、…

数字化时代下,财务共享数据分析建设之路

随着人工智能、云计算、大数据、区块链等技术&#xff0c;以及衍生出的各种产品的大发展&#xff0c;使得数字化发展的速度再一次加快&#xff0c;也让数字经济和数字化转型得到了更多人的关注和认可。 在传统经济增长逐渐放缓&#xff0c;市场竞争愈发激烈的局面下&#xff0…