《深入理解JAVA虚拟机》周志明 第三版 - 第二章 JAVA内存区域与内存溢出异常

一、 概述

在虚拟机自动内存管理机制下,不容易出现内存泄漏和内存溢出问题,但是一旦出现内存泄漏和溢出方面的问题,如果不了解虚拟机是怎样使用内存的,那排查错误、修正问题将会成为一项异常艰难的工作。

二、运行时数据区域

1 程序计数器

Java虚拟机运行时数据区:
在这里插入图片描述
程序计数器(Program Counter Register)是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。改变计数器的值来选取下一条要执行的字节码指令(和计算机CPU一样),各条线程之间的计数器互不影响,独立存储,是“线程”私有的内存。

2 Java虚拟机栈

Java虚拟机栈(Java Virtual Machine Stack)也是线程私有的,它的生命周期与线程相同。每个方法执行的时候,java虚拟机会同步创建一个栈帧,用于存储局部变量表,操作数栈,动态连接,方法出口等信息,每个方法执行的过程,对应一个栈帧在虚拟机中入栈和出栈的过程。

局部变量表存放编译期可知的各种基本数据类型,引用类型和returnAddress类型(指向了一条字节码指令的地址)。

如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;如果Java虚拟机栈容量可以动态扩展[2],当栈扩展时无法申请到足够的内存会抛出OutOfMemoryError异常。

3 本地方法栈

本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,本地方法栈则是为虚拟机使用到的本地(Native)方法服务。

4 Java堆

Java堆(Java Heap)是虚拟机所管理的内存中最大的一块,是被所有线程共享的,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例。

Java堆是垃圾收集器管理的内存区域,因此一些资料中它也被称作“GC堆”,J堆中经常会出现“新生代”“老年代”“永久代”“Eden空间”“From Survivor空间”“To Survivor空间”等名词,这些区域划分仅仅是一部分垃圾收集器的共同特性或者说设计风格而已,而非某个Java虚拟机具体实现的固有内存布局。

5 方法区

方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。

6 运行时常量池

运行时常量池(Runtime Constant Pool)是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池表(Constant Pool Table),用于存放编译期生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。

7 直接内存

直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是《Java虚拟机规范》中定义的内存区域。

三、HotSpot虚拟机对象探秘

1 对象的创建

(1)当Java虚拟机遇到一条字节码new指令时,先去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,那必须先执行相应的类加载过程

(2)在类加载检查通过后,虚拟机将为新生对象分配内存。由Java堆是否规整决定选择哪种分配方式:

  1. 把指针向空闲空间方向挪动一段与对象大小相等的距离,称为“指针碰撞”
  2. 内存不规整,已被使用的内存和空闲的内存相互交错在一起,虚拟机必须维护一个列表来记录上哪些内存块是可用的,分配时从列表中找一块足够大的空间划分给对象实例,并更新列表上的记录,称为“空闲列表”

(3)内存分配完成之后,虚拟机必须将分配到的内存空间(但不包括对象头)都初始化为零值。

(4)对对象进行必要的设置。

2 对象的内存布局

对象在堆内存中的存储布局可以划分为三个部分:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。

(1)对象头:

包括两类信息。分别是:

a.用于存储对象自身的运行时数据,官方称它为“Mark Word”。
在这里插入图片描述

b.类型指针,即对象指向它的类型元数据的指针,Java虚拟机通过这个指针来确定该对象是哪个类的实例。

(2)实例数据

是对象真正存储的有效信息,即我们在程序代码里面所定义的各种类型的字段内容。

(3)对齐填充

并不是必然存在的,也没有特别的含义,它仅仅起着占位符的作用。

3 对象的访问定位

Java程序会通过栈上的reference数据来操作堆上的具体对象,对象访问方式由虚拟机实
现而定,主流的访问方式主要有使用句柄和直接指针两种:

(1)使用句柄访问

Java堆中将可能会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自具体的地址信息。
在这里插入图片描述

(2)使用直接指针访问

Java堆中对象的内存布局就必须考虑如何放置访问类型数据的相关信息,reference中存储的直接就是对象地址,如果只是访问对象本身的话,就不需要多一次间接访问的开销。最大的好处是速度更快,它节省了一次指针定位的时间开销。
在这里插入图片描述

四、实战:OutOfMemoryError异常

【代码实践,没有跟着做,记录一下要点吧】

1 Java堆溢出

2 虚拟机栈和本地方法栈溢出

1)如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常。
2)如果虚拟机的栈内存允许动态扩展,当扩展栈容量无法申请到足够的内存时,将抛出
OutOfMemoryError异常。

3 方法区和运行时常量池溢出

在JDK 8以后,HotSpot还是提供了一些参数作为元空间的防御措施,主要包括:
·-XX:MaxMetaspaceSize:设置元空间最大值,默认是-1,即不限制,或者说只受限于本地内存大小。
·-XX:MetaspaceSize:指定元空间的初始空间大小,以字节为单位,达到该值就会触发垃圾收集进行类型卸载,同时收集器会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过-XX:MaxMetaspaceSize(如果设置了的话)的情况下,适当提高该值。
·-XX:MinMetaspaceFreeRatio:作用是在垃圾收集之后控制最小的元空间剩余容量的百分比,可减少因为元空间不足导致的垃圾收集的频率。类似的还有-XX:Max-MetaspaceFreeRatio,用于控制最大的元空间剩余容量的百分比。

4 本机直接内存溢出

直接内存(Direct Memory)的容量大小可通过-XX:MaxDirectMemorySize参数来指定,如果不去指定,则默认与Java堆最大值(由-Xmx指定)一致。

五、 本章小结

本章只是讲解了各个区域出现内存溢出异常的原因。

先看书记录一下要点,之后看视频的时候会进行补充记录。
(如果能坚持到看视频的话……)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值