JAVA内存区域与内存溢出异常

1.由来

JVM除了使JAVA能够摆脱硬件平台的束缚,实现了“一次编译,到处运行”的 思想,还为JAVA提供了一种相对安全的内存管理和访问机制,相较于C/C++需要维护每一个对象的整个生命周期,要为每一个new或malloc的对象去使用配对的delete/free,JAVA则并不需要考虑这些,JVM提供的垃圾检测回收会帮助我们解决这些,但这并不能说内存泄露和溢出问题就得到完美解决了。相反正是因为内存控制权我们全权给了JVM,所以一旦出现内存泄漏溢出问题,如果不了解JVM的内存使用情况,则很难去排查和解决,因此我们有必要去了解JVM的内存划分使用以及各区域可能出现的问题。

2.内存区域划分

JVM运行时数据区域大致可分为程序计数器,JAVA虚拟机栈,本地方法栈,JAVA堆,方法区。其中,栈和程序计数器都是线程私有的,其生命周期和线程相同,堆和方法区则是线程共享的。
用的别人的图

2.1程序计数器

程序计数器可以视作当前线程所执行字节码的行号指示器,执行的如果是JAVA方法,则是正在执行的虚拟机字节码指令的地址,本地方法则为空。该区域没有产生OutOfMemotyError的情况。(唯一一个)

2.2JAVA虚拟机栈

描述的是JAVA方法执行的线程内存模型:为每一个被执行的方法同步创建一个栈帧用于存储局部变量表、操作数、动态连接、方法出口等信息。方法被调用到执行完毕对应着一个栈帧在虚拟机栈中从入栈到出栈的整个过程。其中局部变量表存放着编译器可知的基本数据类型,对象引用等,它们在局部变量表存储空间中以局部变量槽来表示。
如果线程请求的栈深度大于虚拟机栈所允许的深度,StackOverflowError异常;JAVA虚拟机栈容量可以动态扩展却无法申请到足够内存则OOM异常。

2.3本地方法栈

与 Java 虚拟机栈类似,它们之间的区别只不过是本地方法栈为本地方法服务。
本地方法一般是用其它语言(C、C++ 或汇编语言等)编写的,并且被编译为基于本机硬件和操作系统的程序,对待这些方法需要特别处理。
如果线程请求的栈深度大于虚拟机栈所允许的深度,StackOverflowError异常;JAVA虚拟机栈容量可以动态扩展却无法申请到足够内存则OOM异常。

2.4JAVA堆

虚拟机内存中最大的一块,用于存放对象实例,同时也是垃圾收集器主要管理的内存区域,因此也叫做“GC堆”。
基于分代收集理论,可大致将其分为新生代,老年代。(根据对象存活时间划分,名称叫法有所不同)。
可以处于物理上不连续的内存空间中,但逻辑上应视为连续。
当JAVA堆没有足够内存用于分配对象实例,且无法扩展时,OOM异常。

2.5方法区

用于存储被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码缓存等数据,该区域的内存回收主要是常量池的回收和对类型的卸载,但比较难实现。
方法区无法满足新的内存分配需求时,OOM异常。

2.6运行时常量池

方法区的一部分,Class文件中的常量池表在类加载后存放到该处。
常量池无法申请到足够内存时OOM。

2.7直接内存

在 JDK 1.4 中新引入了 NIO 类,它可以使用 Native 函数库直接分配堆外内存,然后通过 Java 堆里的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在堆内存和堆外内存来回拷贝数据。
不受JAVA堆大小限制,但会受到本机总内存和处理器寻址空间的限制。

3.对象与堆

3.1对象分配方式

对象在堆中的内存分配主要是两种分配方式,一种是“指针碰撞”,一种是“空闲列表”。
“指针碰撞”是假定堆内存绝对规整,未使用过的空闲的内存在一边,使用过的内存在另一边,中间用一个指针作为分界点,要分配内存时就挪动指针;“空闲列表”则假定内存不规整,空闲的和使用过的相互交错,则维护一个列表记录那些内存块可用,分配时找一个足够大的空间给实例对象,并更新记录。
分配方式由堆是否规整决定,后者又由垃圾收集器是否具有空间压缩整理能力决定。

3.2对象内存布局

对象在堆中可划分为对象头、实例数据和对齐填充。
对象头包括对象自身运行时数据,如哈希码、GC分代年龄、锁状态标志等,以及类型指针:对象指向它的类型元数据指针。
实例数据则是对象真正存储的有效信息,即代码中所定义的各种类型的字段内容。
对齐填充则是占位符,本身也不是必要的。

3.3对象访问定位

对象的访问主要是使用句柄和直接指针两种。
句柄:从堆中划分出一块内存作为句柄池,本地变量表中的reference对应的就是对象句柄地址,包含对象实例数据和类型数据的地址信息。
直接指针:reference直接对应对象地址。
总的来说,两者都有一个指向方法区对象类型数据的指针,不同之处在于句柄的对象实例数据额外用一个指针来指向的。
句柄的优点在于对象移动时只需改变对象的实例数据指针,reference不需要改变;直接指针则速度更快,少了一次指针定位的时间开销。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值