第二章 Java内存区域与内存溢出异常
2.3对象的创建
1、检查加载:遇到New指令 ,首先检查这个指令的参数是否能在常量池定位到类的符号引用,并且检查这个符号引用的代表的类是否已经被加载、解析和初始化过;
2、划分内存:为对象分配空间等同于把一块确定大小的内存从Java堆中划分出来,而这时根据内存在堆中的存储方式分为“指针碰撞”和“空闲列表”,但这时在并发情况下并不是线程安全的,解决方法有两种,一种对分配内存空间动作进行同步处理,另一种内存分配的动作按照线程划分在不同的空间中进行,每个线程在Java堆中预先分配一小块内存,称为本地线程分配缓存(TLAB);
3、初始化:内存分配后将分配的内存空间都初始化为0(不包括对象头);
4、对对象进行必要设置(类得元数据信息、对象的哈希码、GV分代)存储在对象头中
对象的内存分布
对象头、实例数据、对齐填充
对象头第一部分用于存储对象自身的运行时数据,如哈希码,GC分代年龄,线程持有的锁等(Mark work),第二部门是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实力
『注』如果对象是一个数组,那在对象头中还必须有一块用于记录对象数组长度的数据,应为虚拟机可以通过普通java对象的元数据信息确定java对象的大小,但是从数组的元数据中无法确定数组的大小,这里也就说明for(int i= arr.length; i>0; i--)和for(int i= 0; i<arr.length;i++)并不会有本质区别。
实例数据:无论从父类继承的还是子类定义的都需要记录下来,受到虚拟机分配策略参数和字段在Java源码中定义顺序的影响;例如相同宽度的字段分派在一起(double,long)
对齐填充并不是必然存在的起着占位符的作用
对象的访问定位
需要通过栈上的reference护具来操作堆上的具体对象。主流的访问方式有两种,句柄和直接指针访问;
句柄访问:堆中会划分出一块内存作为句柄池,reference中存储就是对象的句柄地址,句柄中也包含了对象实例数据与类型数据各自的具体地址信息
直接指针访问,在regerence存储的就是对象的地址
优劣分析:句柄最大的好处就是regerence中存储的是稳定的句柄地址,在对象被移动是只会改变句柄中的实例数据指针,而reference本身不受改变,直接指针的好处就是速度更快,
栈帧(stack frame)是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区中的虚拟机栈的栈元素。栈帧存储了方法的局部变量表、操作数栈、动态连接和方法返回地址等信息。
每一个方法从调用开始到执行完成的过程,就对应着一个栈帧在虚拟机栈里面从入栈到出栈的过程。
对于执行引擎来说,活动线程中,只有栈顶的栈帧是有效的,称为当前栈帧,这个栈帧所关联的方法称为当前方法。执行引擎所运行的所有字节码指令都只针对当前栈帧进行操作。
(1)局部变量表
局部变量表是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。在Java程序被编译成Class文件时,就在方法的Code属性的max_locals数据项中确定了该方法所需要分配的
最大局部变量表的容量。
局部变量表的容量以变量槽(Slot)为最小单位,32位虚拟机中一个Slot可以存放一个32位以内的数据类型(boolean、byte、char、short、int、float、reference和returnAddress八种)。
reference类型虚拟机规范没有明确说明它的长度,但一般来说,虚拟机实现至少都应当能从此引用中直接或者间接地查找到对象在Java堆中的起始地址索引和方法区中的对象类型数据。
returnAddress类型是为字节码指令jsr、jsr_w和ret服务的,它指向了一条字节码指令的地址。
虚拟机是使用局部变量表完成参数值到参数变量列表的传递过程的,如果是实例方法(非static),那么局部变量表的第0位索引的Slot默认是用于传递方法所属对象实例的引用,在方法中通过this访问。
Slot是可以重用的,当Slot中的变量超出了作用域,那么下一次分配Slot的时候,将会覆盖原来的数据。Slot对对象的引用会影响GC(要是被引用,将不会被回收)。
系统不会为局部变量赋予初始值(实例变量和类变量都会被赋予初始值)。也就是说不存在类变量那样的准备阶段。
(2)操作数栈
Java虚拟机的解释执行引擎被称为"基于栈的执行引擎",其中所指的栈就是指-操作数栈。
操作数栈也常被称为操作栈。
和局部变量区一样,操作数栈也是被组织成一个以字长为单位的数组。但是和前者不同的是,它不是通过索引来访问,而是通过标准的栈操作—压栈和出栈—来访问的。比如,如果某个指令把一个值压入到操作数栈中,稍后另一个指令就可以弹出这个值来使用。
虚拟机在操作数栈中存储数据的方式和在局部变量区中是一样的:如int、long、float、double、reference和returnType的存储。对于byte、short以及char类型的值在压入到操作数栈之前,也会被转换为int。
虚拟机把操作数栈作为它的工作区——大多数指令都要从这里弹出数据,执行运算,然后把结果压回操作数栈。比如,iadd指令就要从操作数栈中弹出两个整数,执行加法运算,其结果又压回到操作数栈中,看看下面的示例,它演示了虚拟机是如何把两个int类型的局部变量相加,再把结果保存到第三个局部变量的:
begin
iload_0 // push the int in local variable 0 onto the stack
iload_1 // push the int in local variable 1 onto the stack
iadd // pop two ints, add them, push result
istore_2 // pop int, store into local variable 2
end