第二章
2.2 内存结构
1.方法区 |
它用于存储已被虚拟机加载 的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据 运行时常量池:Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池表(Constant Pool Table),用于存放编译期生 成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。 当常量池无法再申请到内存 时会抛出OutOfMemoryError异常 |
共享 |
2.虚拟机栈 |
——每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态连接、方法出口等信 息。 ——局部变量表:局部变量表存放了编译期可知的各种Java虚拟机基本数据类型(boolean、byte、char、short、int、 float、long、double)、对象引用(reference类型,它并不等同于对象本身,可能是一个指向对象起始 地址的引用指针,也可能是指向一个代表对象的句柄或者其他与此对象相关的位置)和returnAddress 类型(指向了一条字节码指令的地址)。 ——这些数据类型在局部变量表中的存储空间以局部变量槽(Slot)来表示,其中64位长度的long和 double类型的数据会占用两个变量槽,其余的数据类型只占用一个。局部变量表所需的内存空间在编 译期间完成分配,当进入一个方法时,这个方法需要在栈帧中分配多大的局部变量空间是完全确定 的,在方法运行期间不会改变局部变量表的大小。 ——如果线程请求的栈深度大于虚 拟机所允许的深度,将抛出StackOverflowError异常。 ——当栈扩展时无法申请到足够的内存会抛出OutOfMemoryError异常 |
线程私有 |
3.虚拟机堆 |
——“几乎”所有的对象实例都在这里分配内存 ——Java堆是垃圾收集器管理的内存区域,因此一些资料中它也被称作“GC堆” ——如果在Java堆中没有内存完成实例分配,并且堆也无法再 扩展时,Java虚拟机将会抛出OutOfMemoryError异常。 |
共享 |
4.本地方法栈 |
与虚拟机栈所发挥的作用是非常相似的,其区别只是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的本地(Native)方法服务。 | 线程私有 |
| 5.程序计数器 | ——记录下一条需要执行的字节码指令 ——唯一没有OOM情况的区域 |
线程私有 |
直接内存:
2.3 对象创建
1.类加载检查:
当Java虚拟机遇到一条字节码new指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,那必须先执行相应的类加载过程。
2.为对象在堆中分配内存:
两种分配方式:指针碰撞,当使用Serial、ParNew等带压缩 整理过程的收集器时;
空闲列表,使用CMS这种基于清除 (Sweep)算法的收集器
3.分配到的内存空间(但不包括对象头)都初始化为零值
4.设置对象头:
例如这个对象是哪个类的实例、如何才能找到 类的元数据信息、对象的哈希码(实际上对象的哈希码会延后到真正调用Object::hashCode()方法时才 计算)、对象的GC分代年龄等信息。这些信息存放在对象的对象头(Object Header)之中。
5.调用构造函数
注:如何保证多线程下安全:CAS/TLAB
解决这个问题 有两种可选方案:一种是对分配内存空间的动作进行同步处理——实际上虚拟机是采用CAS配上失败 重试的方式保证更新操作的原子性;另外一种是把内存分配的动作按照线程划分在不同的空间之中进 行,即每个线程在Java堆中预先分配一小块内存,称为本地线程分配缓冲(Thread Local Allocation Buffer,TLAB),哪个线程要分配内存,就在哪个线程的本地缓冲区中分配,只有本地缓冲区用完 了,分配新的缓存区时才需要同步锁定。