对象的创建
1. CLass class = new Class();
创建对象(例如克隆、反序列化)通常仅仅是一个new 关键字
2. 创建的过程
a. 根据new指令,首先去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过
没有的话,执行类的加载机制
系统类加载器->扩展类加载器->根类加载器 父委托机制
b. 分配内存
对象所需内存大小在类加载完成后便可完全确定,为对象分配空间的任务等同于把一块确定大小的内存从java堆中划分出来。
1) 第一种分配方式:java堆中内存是绝对规整的,所有用过的内存都放在一边,空闲的内存放到一边,中间放着一个指针
作为分界点的指示器,那所分配内存的就仅仅是把那个指针指向空闲那边挪动一段与对象大小相等的距离,这种分配方式
称为“指针碰撞”
2)第二种分配方式:java堆中的内存不是规整的,已使用的内存和空闲的内存相互交错,虚拟机就必须维护一个列表,记录上那些
内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录,这种分配方式
称为“空闲列表” free list
选择哪种分配方式由java堆是否规整决定,而java堆是否规整又由所采用的垃圾收集器是否带有压缩整理的功能决定。
因此,在使用Serial、PartNew等贷Compact过程的收集器时,系统采用的分配算法是指针碰撞
而使用CMS这种基于Mark-Sweep算法的收集器时,通常采用空闲列表
c. 由于创建对象非常频繁,并发情况下线程不安全如何处理
1)第一种解决方案:对分配内存空间的动作进行同步处理-实际上虚拟机采用CAS配上失败重试的方式保证更新操作的原子性
2)第二种解决方案:把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在java堆中预先分配一小块内存,
称为本地线程分配缓冲(Thread Local Allocation Buffer TLAB).
那个线程要分配内存,就在那个线程的TLAB上分配,只有TLAB用完并分配新的TLAB时,才需要同步锁定。
虚拟机是否使用TLAB,可以通过-XX:+/-UseTLAB参数来设定
d. 内存分配完成后
虚拟机需要将分配到内存空间都初始化为零值(不包括对象头),如果使用TLAB,这一工作过程可以提前至TLAB分配时进行。
这一步操作保证了对象的实例字段在java代码中可以不赋初始值就直接使用,程序能访问到这些的数据类型所对应的零值。
比如:
Class A{
static int i;
static int j=1;
}
第一步类进行加载,第二步连接包含验证:确保被加载类的正确性、准备:为类的静态变量分配内存,并将其初始化
为默认值i=0,j=0、解析:把类中的符号引用转换为直接引用,第三部初始化:为类的静态变量赋予正确的初始值-即j=1
e. 虚拟机对对象进行必要的设置 有点不太懂
例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息。这些信息
存放在对象的对象头(Object Header)之中。根据虚拟机当前的运行状态不同,如是否启用偏向锁等,对象头会有不同的
设置方式。
f. 初始化 init 一般执行new之后接着会执行init