以new关键字创建对象的过程

执行程序Object obj = new Object();

当 jvm 遇到new指令时:

1.
首先检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已经被加载、验证、准备、解析、和初始化过。如果没有那就表明该类还没有被虚拟机加载无法创建,需要先执行类的加载过程。

2. 类加载检查之后,jvm在java堆中按照 “指针碰撞” 或者 “空闲列表” 的方式为新生的对象分配内存,分配内存的大小在类加载完成后便可完全确定。

3.
内存分配完成之后,jvm需要将分配到的内存空间进行初始化零值(不包含对象头),该步骤保证了对象的实例字段在Java代码中可以不赋初始值就可以直接使用,程序能访问到这些字段的数据类型所对应的零值。(实例变量无需初始化即可被程序使用,区别于局部变量的必须初始化才能被程序使用),示例如下:
public class Person { private int anInt; private long aLong; private char
aChar; private byte aByte; private short aShort; private float aFloat; private
double aDouble; private boolean aBoolean; private String aString; // 省略 setter
and getter 方法 } // --------------- public class Test { public static void
main(String[] args){ Person person = new Person();
System.out.println(person.getaByte()); // 0
System.out.println(person.getaShort());// 0
System.out.println(person.getAnInt());// 0
System.out.println(person.getaLong());// 0
System.out.println(person.getaFloat());// 0.0
System.out.println(person.getaDouble());// 0.0
System.out.println(person.getaChar());//
System.out.println(person.getABoolean());// false
System.out.println(person.getaString());// null // 没有给 对象person赋实例变量的值,默认的初始值 }
4. 接下来对对象进行必要的设置:这个对象是哪个类的实例、如何找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息、这些信息存放于对象的对象头信息中。

5. 开始执行<init>方法进行对象的初始化,按照程序员的意愿初始化对象,至此一个真正可用的对象才算产生。

上述对象创建过程中需要了解的知识点或者需要注意的问题

1. Java内存分配的方式——指针碰撞和空闲列表


指针碰撞:在Java堆内存绝对规整的前提下,所有已使用的内存放在一边,空闲的在另一边,中间放着一个指针作为分界点的指示器,那所分配的内存仅仅就是把那个指针向空闲的那边挪动一段与对象大小相等的距离




空闲列表:如果Java堆内存并不是规整的,已使用的内存和未使用的内存交叉,那就没办法按照指针碰撞的方式为新生对象分配内存,jvm必须维护一个列表,记录哪些内存块是可用的,再分配的时候从列表中找到一块足够大的空间会分给对象实例,并更新列表记录,这种分配方式成为“空闲列表”。



Java虚拟机采用哪种方式为对象分配内存取决于Java堆内存是否规整,而Java堆内存是否规整,又取决于采用的垃圾回收器是否带有压缩整理的功能。

2. jvm频繁创建对象所引发的并发问题以及解决方案


对象创建在jvm中是非常频繁的行为,即使是仅仅修改一个指针所指向的位置,在并发情况下也会引发线程安全问题,可能出现正在给对象A分配内存,指针还没来得及修改,对象B又同时使用了原来的指针来分配内存的情况。解决这个问题有两个方案:1.
对分配内存空间的操作进行同步处理——实际上虚拟机采用CAS配上失败重试的方式保证更新操作的原子性;另一种是把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在Java堆中预先分配一小块内存,成为本地线程分配缓冲(Thread Local Allocation Buffer,TLAB)。哪个线程要分配就在哪个线程的TLAB上分配,只有TLAB用完并分配新的TLAB时,才需要同步锁.