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

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

2019-03-16 16:41 218 查看

第二部分 自动内存管理机制

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

2.2 运行时数据区域

  方法区(Method Area)、虚拟机栈(VM Stack)、本地方法栈(Native Method Stack)、堆(Heap)、程序计数器(Program Counter Register)。

2.2.1 程序计数器

程序计数器是一块较小的内存空间,它可以被看作是当前线程所执行的字节码的行号指示器。每条线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响, 独立存储,我们称这类内存区域为“线程私有”的内存。

2.2.2 Java虚拟机栈

  Java虚拟机栈也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用reference类型和returnAddress类型。

2.2.3 本地方法栈

本地方法栈(Native Method Stack)与虚拟机栈所发挥的作用是非常相似的,它们之间的区别不过就是虚拟机栈为虚拟机执行Java方法服务,而本地方法栈则为虚拟机使用到Native方法服务。

2.2.4 Java堆

  Java堆(Java Heap)是Java虚拟机所管理的内存中最大的一块,是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。Java堆是垃圾收集器管理的主要区域,因此也被称为"GC堆"。Java堆还可以细分为:新生代和老生代;再细致一点的有Eden、From Survivor、To Survivor等。

2.2.5 方法区

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

2.2.6 运行时常量池

  运行时常量池是方法区的一部分。用于存放编译期生成的各种字面量和符号引用,这部分将在类加载后进入方法区的运行时常量池中存放。运行时常量池相对于Class文件常量池的重要特征就是:Java虚拟机规范没有做任何细节的要求和具备动态性(运行期间也可能将新的常量放入池中)。

2.2.7 直接内存

  直接内存并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域。但是这部分内存也被频繁地使用,而且也可能导致OutOfMemoryError异常出现。本机直接内存的分配不会受到Java堆大小的限制,但是既然是内存,肯定还是会受到本机总内存大小以及处理器寻址空间的限制。

2.3 HotSpot虚拟机对象探秘

2.3.1 对象的创建

   1. 检查new指令的参数能否在常量池中定位到一个类的符号引用,并且检查代表的类是否被加载、解析和初始化过。
   2. 如果没有,那必须先执行相应的类加载过程。
   3. 在类加载检查通过后,虚拟机将为新生对象分配内存。
  分配方式有两种:根据Java堆中内存是否规整分为"指针碰撞"(绝对规整)和"空闲列表"(不规整)。而Java堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能绝对,使用Serial、ParNew等带Compact过程的收集器时,系统采用的分配算法是指针碰撞,而使用CMS这种基于Mark-Sweep算法的收集器时,通常采用空闲列表。
  对象创建在虚拟机中是非常频繁的行为,在并发情况下也并不是线程安全的。解决这个问题有两种方案,一种是对分配内存空间的动作进行同步处理——采用CAS配上失败重试的方式;另一种是把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在Java堆中预先分配一小块内存,称为本地线程分配缓冲TLAB。

2.3.2 对象的内存布局

  对象在内存中存储的布局可以分为3块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。
  对象头包括两部分信息,第一部分用于存储对象自身的运行时的数据,如哈希码(HashCode)、GC分代年龄、锁状态标致、线程持有的锁、偏向线程ID、偏向时间戳等,第二部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
  实例数据:对象真正存储的有效信息,也是在程序代码中所定义的各种类型的字段内容。这部分的存储顺序会受到虚拟机分配策略参数(FieldAllocationStyle)和字段在Java源码中定义顺序的影响。
HotSpot虚拟机默认的分配策略为longs/doubles、ints、shorts/chars、bytes/booleans、oops(Ordinary Object Pointers),从分配策略中可以看出,相同宽度的字段总是被分配到一起。
  对齐填充:起着占位符的作用,由于HotSpot VM的自动内存管理系统要求对象起始地址必须是8字节的整数倍,对象的大小必须是8字节的整数倍,而对象头部分正好是8字节的倍数(1倍或者2倍),因此,当对象实例数据部分没有对齐时,需要通过对齐填充来补全。

2.3.3 对象的访问定位

  主流的访问方式有句柄和直接指针两种。

  • 句柄访问:Java堆中将会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息。
  • 直接指针访问:Java堆对象的布局中就必须考入如何放置访问类型数据的相关信息,而reference中存储的直接就是对象地址。

  句柄访问的好处就是reference中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而reference本身不需要改变。
  使用直接指针访问方式的最大好处就是速度更快,它节省了一次指针定位的时间开销,虚拟机Sun HotSpot时使用直接指针进行对象访问的。

2.4.2 虚拟机栈与本地方法栈溢出

  • 如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常
  • 如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐