对象的创建
对象的创建方式
-
使用new关键字创建对象
Java java = new Java();
-
使用Class类的newInstance方法(反射机制)
Java java = (Java)Class.forName("Java类全限定名").newInstance();
Java java = Java.class.newInstance();
-
使用Constructor类的newInstance方法(反射机制)
java.lang.relect.Constructor类里也有一个newInstance方法可以创建对象,该方法和Class类中的newInstance方法很像,但是相比之下,Constructor类的newInstance方法更加强大些,我们可以通过这个newInstance方法调用有参数的和私有的构造函数
public class Java { private int id; public Java(Integer id) { this.id = id; } public static void main(String[] args) throws Exception { Constructor<Java> constructor = Java.class .getConstructor(Integer.class); Java java = constructor.newInstance(1024); } }
-
使用clone方法(类需要实现Cloneable接口)
-
使用(反)序列化机制创建对象(需要类实现Serializable接口)
对象的创建过程
没当一个对象创建时,虚拟机会为其分配内存,其次进行初始化.
- 虚拟机分配内存
- 对象初始化
虚拟机分配内存
存放类实例变量及其从父类继承过来的的实例变量(即使从超类继承过来的实例变量有可能被隐藏也会被分配空间),在为其分配内存时,这些变量也会被赋予其变量属性的默认值.
对象初始化
-
实例变量初始化
-
实例代码块初始化
-
构造函数初始化
类初始化执行顺序
/** * @ClassName Java * @Description 测试执行顺序 * @Author XiaoZhe * @Date 2020/5/25 10:07 * @Version 1.0 **/ public class Java { /** * 实例变量 */ private int i = 1024; /** * 静态变量 */ private static int j = 2048; /** * (构造代码块)实例代码块 */ { System.out.println("执行实例代码块 - "); } /** * 静态代码块 */ static { /** * 随着类的加载,只执行一次,并由于主函数(由类调用) * 静态代码块是类初始化时用, 构造代码块(实例代码块)是对象初始化 */ System.out.println("静态变量 - "+j); System.out.println("执行静态代码块 - "); } /** * 无参构造方法 */ public Java() { System.out.println("执行无参构造方法 - "); } public static void main(String[] args) { //Java java = new Java(); }
输出 :
静态变量 - 2048 执行静态代码块 -
对象初始化执行顺序
将上面主函数中的创建对象的注释去掉,并执行,获得下方结果:
静态变量 - 2048 执行静态代码块 - 执行实例代码块 - 执行无参构造方法 -
在上面主函数中再创建一个对象加入下面代码 :
Java java2 = new Java();
结果:
静态变量 - 2048 执行静态代码块 - 执行实例代码块 - 执行无参构造方法 - 执行实例代码块 - 执行无参构造方法 -
涉及到继承
当涉及到继承时,按照如下顺序执行: 执行父类的静态代码块,并初始化父类静态成员变量 执行子类的静态代码块,并初始化子类静态成员变量 执行父类的构造代码块,执行父类的构造函数,并初始化父类普通成员变量 执行子类的构造代码块, 执行子类的构造函数,并初始化子类普通成员变量
public class JavaA { public JavaA(){//构造函数 System.out.println("A的构造函数"); } {//构造代码块 System.out.println("A的构造代码块"); } static {//静态代码块 System.out.println("A的静态代码块"); } } public class JavaB extends JavaA{ public JavaB(){//构造函数 System.out.println("B的构造函数"); } {//构造代码块 System.out.println("B的构造代码块"); } static {//静态代码块 System.out.println("B的静态代码块"); } public static void main(String[] args) { JavaB b=new JavaB(); } } 运行结果: A的静态代码块 B的静态代码块 A的构造代码块 A的构造函数 B的构造代码块 B的构造函数
可以看出静态变量优于静态代码块执行,子类的静态变量或静态代码块优于父类构造代码块和构造函数执行.
实例化一个类的对象的过程是一个典型的递归过程
针对于向上无限继承,在初始化最底层的类时,便会如递归似得向上一个一个寻找父类直到寻到顶层父类,到达顶层父类后,不断向下归来并同时实例化各相关类.
对象的创建过程
-
构建对象
根据Java类元信息确定对象的大小,向JVM堆中申请一块内存区域并构建对象的默认信息(加载Java对象成员变量信息并赋默认值如 int类型为0,引用类型为null)。
-
初始化对象:
然后执行对象内部生成的init方法,初始化成员变量值,同时执行搜集到的{}代码块逻辑,最后执行对象构造方法。
-
引用对象:
对象实例化完毕后,再把栈中的Java对象引用地址指向Java对象在堆内存中的地址。
在内存区域中对象是什么形式的哪?
HotSpot虚拟机中,对象在内存中存储的布局可以分为三块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding).
对象头
对象头里主要包括几类信息,分别是锁状态标志、持有锁的线程ID、,GC分代年龄、对象HashCode,类元信息地址、数组长度,这里并没有对对象头里的每个信息都列出而是进行大致的分类,下面是对其中几类信息进行说明。
-
锁状态标志: 对象的加锁状态分为无锁、偏向锁、轻量级锁、重量级锁几种标记。
-
持有锁的线程: 持有当前对象锁定的线程ID。
-
GC分代年龄: 对象每经过一次GC还存活下来了,GC年龄就加1。
-
类元信息地址: 可通过对象找到类元信息,用于定位对象类型。
-
数组长度: 当对象是数组类型的时候会记录数组的长度。
这里要特别关注的是**锁标志位,**锁标志位与是否偏向锁对应到唯一的锁状态。
由此可以了解Synchronized锁住的是对象,就是通过对象头中的这个锁状态标志来锁住对象.
实例对象
对象实例数据才是对象的自身真正的数据,主要包括自身的成员变量信息,同时还包括实现的接口、父类的成员变量信息.
对齐填充
根据JVM规范对象申请的内存地址必须是8的倍数,换句话说对象在申请内存大小时候8字节的倍数,如果对象自身的信息大小没有达到申请的内存大小,那么这部分是对剩余部分进行填充.