您的位置:首页 > 编程语言 > Java开发

第二章 Java内存区域与内存溢出异常

2017-09-04 22:44 218 查看
内存区域划分

Java内存区域分为线程间共享区域和线程间隔离区域,其中线程间共享区域又划分为堆和方法区两种;线程间隔离区域即线程独占区域,又划分为虚拟机栈、本地方法栈和程序计数器。下面对各区域进行详细讲解:

程序计数器

程序计数器是一块较小的空间,可以看作是当前线程所执行的字节码的行号指示器。字节码解释器工作就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要以来这个技术器来完成。

如果线程正在执行的是一个Java方法,计数器记录的就是正在执行的虚拟机字节码指令的地址;如果正在执行的是Native方法,计数器之则为空(Undefined)。此内存区域是唯一一个在JVM规范中没有规定任何OOME(OutOfMemoryError)情况的内存区域。

Java虚拟机栈

Java虚拟机栈的生命周期与线程相同。描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机战中入栈到出栈的过程。

局部变量表存放了在编译期可知的各种基本类型、对象引用和returnAddress类型。其中,64位的long和double类型的数据会占用2个局部变量空间(slot)。局部变量空间的大小在编译期是完全确定的。

该区域有两种异常:StackOverflowError,线程请求的栈深度大于虚拟机所允许的深度;OutOfMemoryError,虚拟机栈动态扩展无法申请到足够的内存。

本地方法栈

与Java虚拟机栈类似,本地方法栈是专门为本地方法服务的,本地方法通常是用非Java方法编写的程序,也会抛出StackOverflowError和OutOfMemoryError异常。

Java堆

Java堆(Heap)是Java虚拟机所管理的内存中最大的一块,被所有线程所共享,在虚拟机启动时创建,主要用于存储所有的对象实例和数组。

方法区

用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

为了方便对方法区的内存进行管理,有的虚拟机把该区域用永久代(永久代上线设置-XX:MaxPermSize)来实现。该区域内存回收的目标主要是针对常量池的回收和对类型的卸载。当该区域无法满足内存分配需求是,将抛出OutOfMemoryError异常。

运行时常量池作为方法区的一部分,用于在类加载后存放生成的各种字面量和符号引用,包括编译器和非编译期(动态性)。

附-直接内存

直接内存与Java堆同属于机器内存的一部分,通过Native函数库直接分配堆外内存。

对象的内存布局

在HotSpot虚拟机中,对象在内存中存储的部署可以分为3块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。

对象头包括三部分:用于存储对象自身的运行时数据“Mark Word”,包括哈希码、GC分代年龄等;第二部分是类型指针,即对象指向它的类元数据的指针,用来确定该对象是哪个类的实力。如果对象是一个Java数组,那么在对象骰盅还必须有一块用于记录数组长度的数据,因为虚拟机可以通过普通Java对象的元数据信息确定Java对象的大小,但是数组不能;第三部分不是必然存在的,只是起着占位符的作用。

对象的访问定位

对象主要包括两部分数据:实例数据和类型数据。

- 使用句柄

Java堆中划分出一块内存作为句柄池,reference引用存储的就是对象的句柄地址,而句柄则包含了对象实例数据和类型数据各自的具体地址。

- 直接指针

类型数据存放在对象中,对象类型数据的地址存放在对象中;reference引用存放的就是对象的具体地址。

对比:句柄访问的最大好处是reference中存储的是稳定的句柄地址,在对象移动式只会改变句柄中的实例数据指针,而reference本身不需要修改;直接指针访问的最大好处是速度快,因为它节省了一次指针定位的时间开销。

堆大小设置:-Xms设置最小值;-Xmx设置最大值。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: