初学JVM虚拟机──内存区域与内存溢出异常

运行时数据区域

在这里插入图片描述
上图中黄色部分是线程私有的

程序计数器

  1. 程序控制流的指示器,基础的分支、循环、跳转、异常处理、线程恢复等功能都依赖它
  2. 在java虚拟机的概念模型中,字节码解释器工作时就是通过改变它(计数器)的值,来选取下一条需要执行的字节码指令
  3. 它在java虚拟机中是线程私有的。原因:一个处理器的各条线程进行工作时,为了保证它们切换后都能恢复到正确的执行位置,每个线程都需要有独立的程序计数器。各个程序计数器间不能互相干扰
  4. 如果线程正在执行一个java方法,则这个计数器记录的是正在执行的字节码指令的地址;如果执行的是本地(native)方法,这个计数器值则为空(Undefined)。
  5. 它不会出现OutOfMemoryError错误。原因:它仅仅是储存一条指令的地址,这个地址总不会超出这块内存的大小,所以不会出现内存溢出

java虚拟机栈

  • 为虚拟机执行java方法(也就是字节码)服务

  • 同样它也是线程私有的

  • 它的生命周期和线程同样

  • 它描述的是java方法执行的线程内存模型:每个方法执行的时候,java虚拟机都会同步创建一个栈帧用来存储局部变量表操作数栈动态连接方法出口等信息。每一个方法被调用直至执行完毕的过程,就对应一个栈帧在虚拟机栈中从入栈到出栈的过程。

    通常我们讲的“栈”就是指这里的虚拟机栈,或者更多的情况下仅指虚拟机栈中局部变量表的部分

局部变量表

  • 局部变量表存放了编译期可知的各种java虚拟机基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用和returnAddress类型(指向一条字节码指令地址)

对象引用,并不是对象本身,它可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或者其他与此对象有关的位置

  • 上述的数据类型在局部变量表中的存储空间以局部变量槽(Slot)表示。64位长度的long和double类型占用两个Slot,其余占用一个。
  • 局部变量表所需的内存空间在编译期完成分配,当进入一个方法时,这个方法需要在栈帧分配的局部变量空间是完全确定的,方法运行期间不会改变局部变量表的大小。这里的大小是指变量槽的数量。

StrackOverflowError:线程请求的栈深度大于虚拟机所允许的深度时,抛出该异常
OutOfMemoryError:虚拟机栈容量是可以动态扩展的(有的虚拟机实现是不行的例如HotSpot),当栈扩展时无法申请到足够的内存时,抛出该异常

本地方法栈

其与虚拟机栈所发挥的作用非常相似,本地方法栈为虚拟机提供使用本地方法(native)服务

java堆

  • java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建
  • 该内存块唯一的目的就是存放对象实例,java中“几乎”所有的对象实例都在这里分配内存

java虚拟机规范中对java堆的描述:所有的对象实例以及数组都应当在堆上分配

这里用到几乎,而未用全部,则是考虑到现在java的发展,现在已经初见端倪了,日后可能出现值类型的支持,且目前来说,由于即时编译技术的进步,尤其是逃逸分析技术的日渐强大,栈上分配、标量替换优化手段已经导致了一些微妙的变化,所以java对象实例都分配在堆上也渐渐变得不那么绝对了

  • 另一点,有的资料上提到的“新生代、老年代、永久代”等等一些内容,在十年前(以G1收集器出现为分界),是不太会有歧义,但到了今天垃圾回收技术已经不可同日而语了,HotSpot里面也出现了不采用分代设计的新垃圾收集器,在按照上面的说话就不太合适了
  • java堆可以处于物理上不连续的内存空间中,但在逻辑上应该视它为连续的,所以java堆即可以被实现成固定大小的,也可以进行扩展。也就是我们可以通过

-Xmx 和-Xms进行设定

  • 如果java堆没有完成实例分配,且堆也无法再进行扩展时,虚拟机会抛出OutOfMemeryError异常

方法区

  • 方法区与堆一样,是各个线程共享的内存区域,它用来存储已经被虚拟机加载的类型信息、常量、静态变量、即时编译后的代码缓存等数据
  • 方法区在java8以前,HotSPot虚拟机设计团队将其设置成“永久代”,现在看来是不合理的,这就导致,其极易出现内存泄漏,永久代有 -xx:MaxPerSize的上限,即使不设置也有默认大小
  • 在java虚拟机规范中,对方法区的约束是非常宽松的,除了和堆一样不需要连续的内存和可以选择固定大小或者可扩展外,甚至还可以选择不实现垃圾回收。
  • 方法区中,垃圾回收在方法区确实不太需要,但是不是数据进入了方法区就如永生代一样“永久存在了”。
  • 方法区无法满足新的内存分配需求时,将抛出OutOfMemoryError异常

运行时常量池

  • 运行时常量池是方法区的一部分。
  • Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项是常量池表,用于存放编译期生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。
  • 虚拟机对Class文件的每一部分(包括常量池)的格式都有严格规定,只有符合规范,才能被虚拟机认可、加载和执行。但对于运行时常量池,《java虚拟机规范》并没有做任何细节的要求。

直接内存

  • 直接内存并不是虚拟机运行时数据区的一部分,也不是《java虚拟机规范》中定义的内存区域。但是这部分内存也被频繁地使用,而且也可能导致OutOfMemeryError的出现。
  • jdk4中新加入了NIO(New Input/Output)类,引入了一种基于通道与缓冲区的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。这样可以避免在java堆和native堆中来回复制数据。
  • 另外,虽然直接内存不会收到jvm堆大小的限制,但是,它既然是内存,就会受到当前机器的总体内存大小以及处理器寻址空间的限制。所以我们在配置java堆内存时需要考虑直接内存,否则会导致动态扩展时出现OutOfMemoryError异常

HotSpot虚拟机对象

简介

  • 目前,JVM中最广泛应用的虚拟机是HotSpot
  • 为了解决虚拟机中内存数据如何创建,如何布局,如何访问等的问题,这里就以HotSpot虚拟机和java堆举例

对象的创建

创建对象对于我们(java程序猿)来说,new这个关键字是最常用的,这里我们不讨论数组和Class对象.

  1. 当java虚拟机遇到一条字节码new指令时,先检查这个指令的参数是否能再常量池中定位到一个类的符号引用,并检查这个符号引用代表的类是否已经被加载、解析和被初始化过。如果没有,则执行类加载过程。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qlanto

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值