Java如何创建一个对象

Java

嗯?

1
new Object();

结束
dog
哈哈,开个玩笑,本篇文章主要是大概讲一下在Java中,创建一个对象的过程发生了什么,以及有哪些方法可以创建对象,类似文章百度一下可以找到很多,本篇也仅是自己为了加深记忆,文章内容大部分转载自《Java类加载及对象创建过程详解》《今天必须要完成一件大事!》

对象的初始化顺序

文章先从对象的初始化顺序开始整理,一个类如果没有继承其它类,那么它的初始化过程如下:

1
2
3
4
5
静态属性
静态代码块
普通属性
普通代码块
构造函数

一个类如果有父类,那么它的初始化过程如下:

1
2
3
4
5
6
7
8
9
10
父类静态属性
父类静态代码块
子类静态变量
子类静态代码块
父类普通属性
父类代码块
父类构造函数
子类属性
子类代码块
子类构造函数

类的加载过程

Java中类的加载分为五个过程:

  • 加载
  • 验证
  • 准备
  • 解析
  • 初始化

加载

在加载阶段,虚拟机主要完成三件事:

  1. 通过一个类的全限定名来获取定义此类的二进制字节流
  2. 将这个字节流所代表的静态存储结构转化为方法区域的运行时数据结构
  3. Java堆中生成一个代表这个类的Class对象,作为方法区域数据的访问入口。

    验证

    验证阶段是为了确保Class文件的字节流包含的信息符合JVM规范,不会给JVM造成危害,如果验证失败,会抛出一个java.lang.VerifyError异常或其子类异常。验证过程分为四个阶段:
  4. 文件格式验证:验证字节流文件是否符合Class文件格式的规范,并且能被当前虚拟机处理
  5. 元数据验证:是对字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言的规范要求
  6. 字节码验证:主要是进行数据流和控制流的分析,保证被校验类的方法在运行时不会危害虚拟机。
  7. 符号引用验证:符号引用验证发生在虚拟机将符号引用转化为直接引用的时候,这个转化动作将在解析阶段发生

    准备

    准备阶段为变量分配内存并设置类变量的初始化。在这个阶段分配的仅为类的变量(static修饰的变量)而不包含实例变量。对非final变量,JVM会将其设置成该类型的零值,例如private static int len=12;在这个阶段的时候,len的值为0而不是12,但是final修饰的类变量将会赋值成真实的值。

    解析

    解析过程是将常量池内的符号引用替换成直接引用。主要包括四种类型引用的解析:类或接口的解析、字段解析、方法解析、接口方法解析。

    初始化

    在准备阶段,类变量已经经过一次初始化了,在这个阶段,则是通过程序制定的计划去初始化变量和其他资源。这些资源有static{}块、构造函数、父类的初始化等等。

    双亲委派模型

    类加载器按照层次,从顶层到底层,分为以下三种:
  8. 启动类加载器(BootstrapClassLoader)

    这个类加载器负责加载%JRE_HOME%\lib下的rt.jarresources.jarcharsets.jarclass等。可以通过System.getProperty("sun.boot.class.path")查看加载的路径。

  9. 扩展类加载器(ExtentionClassLoader)

    负责加载目录%JRE_HOME%\lib\ext目录下的jar包和class文件。也可以通过System.out.prinln(System.getProperty("java.ext.dirs"))查看加载类文件的路径

  10. 应用程序类加载器(AppClassLoader )

    这个加载器是ClassLoadergetClassLoader()方法的返回值,所以一般也称它为系统类加载器。它负责加载用户类路径(Classpath)上所指定的类库,可直接使用这个加载器,如果应用程序没有自定义自己的类加载器,一般情况下这个就是程序中默认的类加载。

关于启动类加载器、扩展类加载器、应用类加载器详解

工作过程

如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传递到顶层的启动类加载器中,只有当父类加载器反馈自己无法完成这个请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。
具体示例:假如JVM需要加载Test.class

  1. 首先会到自定义加载器中查找,看是否已经加载过,如果加载过,则返回字节码。
  2. 如果自定义加载器没有加载过,则询问上一层加载器(即AppClassLoader)是否已经加载过Test.class
  3. 如果没有加载过,则询问上一层(ExtClassLoader)是否已经加载过
  4. 如果没有加载过,则继续询问上一层(BootStrap ClassLoader)是否已经加载过。
  5. 如果BootStrap ClassLoader依然没有加载过,则到自己指定的加载路径下"sun.boot.class.path"查看是否有Test.class字节码,有则返回,没有则通知下一层加载器ExtClassLoader到自己指定的类加载路径java.ext.dirs查看
    依次类推,最后到自定义类加载器指定的路径还没有找到字节码的话,则抛出异常ClassNotFoundException

    双亲委派的好处

    Java类随着它的类加载器一起具备了一种带有优先级的层次关系。例如类Object,它放在rt.jar中,无论哪一个类加载器要加载这个类,最终都是委派给启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都是同一个类
    判断两个类是否相同是通过classloader.class这种方式进行的,所以哪怕是同一个class文件如果被两个classloader加载,那么他们也是不同的类。

创建对象的方式

好了,上面叨叨了一堆,接下来讲一下创建对象的方法有哪些

new一个对象

用关键字new进行对象的创建,是平时最常见的创建对象方式,比如:

1
Object o = new Object();

反射一个对象

一般只要能拿到类的Class对象,就可以通过反射机制来创造出实例对象,拿到Class对象的方式有三种:

  1. 类名.class
  2. 对象名.getClass()
  3. Class.forName(全限定类名)
    有了Class对象之后,接下来就可以调用其newInstance()方法来创建一个对象:
    1
    2
    Luoyang ly1 = (Luoyang) Class.forName("wiki.luxiang.blog.Luoyang").newInstance();
    Luoyang ly2 = Luoyang.class.newInstance();

    克隆出一个对象

    写代码时候经常会用到相关的操作,具体后面整理到相关知识点会进行补充,未完待续。。。。

    反序列化一个对象

    写代码时候经常会用到相关的操作,具体后面整理到相关知识点会进行补充,未完待续。。。。

    Unsafe

    1
    2
    3
    4
    Field f = Unsafe.class.getDeclaredField("theUnsafe");
    f.setAccessible(true);
    Unsafe unsafe = (Unsafe) f.get(null);
    Luoyang ly3 = (Luoyang) unsafe.allocateInstance( Luoyang.class );

    对象的隐式创建

    即常见的自动装箱机制
    1
    2
    String name = "luoyang";
    Integer age = 1;
    好了,本篇到这里也就结束了,再次强调,内容非原创,主要是回顾知识过程中做的记录,原文:《Java类加载及对象创建过程详解》《今天必须要完成一件大事!》写的更详细。