目录
Java内存区域与内存溢出异常
Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的“高墙”,墙外面的人想进去,墙里面的人却想出来。
2.1 概述
2.2 运行时数据区域
2.2.1 程序计数器
2.2.2 Java虚拟机栈
2.2.3 本地方法栈
2.2.4 Java堆
2.2.5 方法区
方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做Non-Heap(非堆),目的应该是与Java堆区分开来。
2.2.6 运行时常量池
运行时常量池(Runtime Constant Pool)是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。
Java虚拟机对Class文件每一部分(自然也包括常量池)的格式都有严格规定,每一个字节用于存储哪种数据都必须符合规范上的要求才会被虚拟机认可、装载和执行,但对于运行时常量池,Java虚拟机规范没有做任何细节的要求,不同的提供商实现的虚拟机可以按照自己的需要来实现这个内存区域。不过,一般来说,除了保存Class文件中描述的符号引用外,还会把翻译出来的直接引用也存储在运行时常量池中。
运行时常量池相对于Class文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定是编译期才产生,也就是并非预置入Class文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中,这种特性被开发人员利用的比较多的便是Stirng类的intern()方法。
既然运行时常量池是方法区的一部分,自然受到方法区内存的限制,当常量池无法再申请到内存时会抛出OutOfMemoryError异常。
2.2.7 直接内存
2.3 HotSpot虚拟机对象探秘
2.3.1 对象的创建
2.3.2 对象的内存布局
在HotSpot虚拟机中,对象在内存中存储的布局可以分为3块区域:对象头(Header)(对象自身的运行时数据、类型指针)、实例数据(Instance Data)和对齐填充(Padding)。
HotSpot虚拟机的对象头包括两部分信息,第一部分用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,这部分数据的长度在32位和64位的虚拟机(未开启压缩指针)中分别位32bit和64bit,官方称它为“Mark Word”。对象需要存储的运行时数据很多,其实已经超出了32位、64位的Bitmap结构所能记录的限度,但是对象头信息是与对象自身定义的数据无关的额外存储成本,考虑到虚拟机空间的效率,Mark Word被设计成一个非固定的数据结构以便在极小HotSpot虚拟机中,如果对象处于未被锁定的状态下,那么Mark Word的32bit空间中的25bit用于存储对象哈希码,4bit用于存储对象分代年龄,2bit用于存储锁标志位,1bit固定位0,而在其他状态(轻量级锁定、重量级锁定、GC标记、可偏向)下对象的存储内容见表2-1。
存储内容 | 标志位 | 固定位 | 状态 |
---|---|---|---|
对象哈希码(25bit)、对象分代年龄(4bit) | 01(2bit) | 0(1bit) | 未锁定 |
指向锁记录的指针 | 00 | 轻量级锁定 | |
指向重量级锁的指针 | 10 | 膨胀(重量级锁定) | |
空,不需要记录信息 | 11 | GC标记 | |
偏向线程ID、偏向时间戳、对象分代年龄 | 01 | 可偏向 |
对象头的另外一部分是类型指针(配合2.3.3 对象的访问定位:直接指针来理解),即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。 并不是所有的虚拟机实现都必须在对象数据上保留类型指针,换句话说,查找对象的元数据信息并不一定要经过对象本身,这点将在2.3.3节讨论。另外,如果对象是一个java数组,那在对象中还必须有一块用于记录长度的数据,因为虚拟机可以通过普通Java对象的元数据信息确定Java对象的大小,但是从数组的元数据中却无法确定数组的大小。
代码清单。。。
接下来的实例数据部分。。。
第三部分对齐填充。。。