对象的创建过程
在语言层面上,创建对象(例如克隆、反序列化)只是一个new关键字而已,但对于虚拟机来说,会进行一系列的操作(不包括数组和Class对象):
- 当虚拟机遇到一条new指令时,会去常量池中检查是否有这个类的符号引用
- 若常量池中没有这个类的符号引用,那么这个类还没有被定义,直接抛ClassNotFoundException;
- 若是有符号引用, 那么进行下一步操作。
- 检查这个符号引用代表的类是否已经被jvm加载?
- 如果该类还没有被jvm加载,就找该类的class文件,加载进方法区。
- 已经加载过, 准备为该类分配内存空间
- 根据方法区中该类的信息确定所需内存大小。
一个对象所需的内存大小在该类加载完毕之后就能够完全确定,并且该类所生产的对象大小是完全一致的。
- 从java堆中划分一块确定大小的内存分配给该对象。
有两种分配方式:
1. “指针碰撞”:假设java堆内存是绝对规整的,用过的内存放在一边, 没用过的放在另一边,中间放着一个指针作为 分界点的指示器,那么这种分配方式相当于把那个指针向空闲空间那边移动一段与该对象大小相等距离。
2. “空闲列表”:这种方式是 使用过的和没用过的内存相互交错。虚拟机维护一个列表,记录上那些内存块是可用的,哪 些是没有用过的,给该对象分配时从列表中找出一块足够大的空间交给该对象。
需要考虑的问题: 多线程并发出错问题。
对象的创建在虚拟机中是非常频繁的行为,可能会出现正在给A对象分配内存,指针还没来得及修改,对象B又使用了原来的指针来分配内存的情况。
解决方案:
1. 同步处理:对分配内存空间的动作进行同步处理,实际上jvm是采用CAS配上失败重试的方式保证更新操作的原子(题外话: 由于近期看了jdk里面的ConcurrentHashMap的源码,发现jdk1.8里面的实现也是CAS加上失败重试,看来类似的问题有着类似的解法)
2. 把内存分配的动作按照线程划分在不同的空间之中进行。
- 内存分配完成后,虚拟机将分配到的内存空间初始化为0(不包括对象头)。这一步保证了对象的实例字段在java代码中不赋初值就可以直接使用。
- 虚拟机对对象进行必要的设置(对象头的设置), 例如这个对象是那个类的实例、如何找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息。
- 对于虚拟机来说新的对象已经生成了,但从java程序的角度来看,对象创建才刚刚开始,需要执行<init>方法。