精华内容
下载资源
问答
  • Java对象的创建过程

    2019-11-28 22:07:14
    Java对象的创建过程与初始化过程 此文聚焦“对象”而不是“类” 1、Java对象创建过程 图片来源:https://www.cnblogs.com/chenyangyao/p/5296807.html 2、Java对象的初始化(类的实例化) 类的初始化不在本文范畴 此...

    Java对象的创建过程与初始化过程

    此文聚焦“对象”而不是“类”

    1、Java对象创建过程

    图片来源:https://www.cnblogs.com/chenyangyao/p/5296807.html

    在这里插入图片描述

    2、Java对象的初始化(类的实例化)

    类的初始化不在本文范畴

    此部分对应上图的调用对象的<init>方法<init>()又叫做实例构造器,与之类似的有<clinit>()称为类构造器(用来初始化类,具体见《JVM类的生命周期与类的加载过程》)。

    注意:在此之前,在虚拟机为对象分配内存后,已经进行过一次初始化了

    然后才真正按照程序员的意志进行初始化:

    1. 实例变量初始化
    2. 实例代码块初始化
    3. 构造函数初始化

    1)实例变量初始化与实例代码块初始化

    二者地位相同,编译器会按照二者的相对顺序将其插入到类的构造函数中,位置在超类构造函数之后(如果显示调用了),本构造函数体中的剩余代码之前。

    举例:

    public class InstanceVariableInitializer {  
    
        private int i = 1;  
        private int j = i + 1;  
    
        public InstanceVariableInitializer(int var){
            System.out.println(i);
            System.out.println(j);
            this.i = var;
            System.out.println(i);
            System.out.println(j);
        }
    
        {               // 实例代码块
            j += 3; 
    
        }
    
        public static void main(String[] args) {
            new InstanceVariableInitializer(8);
        }
    }
    /* Output: 
                1
                5
                8
                5
     *///:~
    

    上例的构造函数等价于:

    public InstanceVariableInitializer(int var){
            // 这里隐含了super.InstanceVariableInitializer()
      			// 以下是实例变量与实例代码块等价整合到了构造函数中
    				this.i = 1;
      			this.j = i + 1;
      			this.j = this.j + 3;
      			// 本构造函数的函数体
            System.out.println(i);
            System.out.println(j);
            this.i = var;
            System.out.println(i);
            System.out.println(j);
        }
    

    通过查看字节码也可以验证这种整合:,如2)中的第一个例子中的字节码就有体现。
    注意:不允许顺序靠前的实例代码块初始化在其后面定义的实例变量,如:

    编译不通过

    public class InstanceInitializer {  
        {  
            j = i;  
        }  
    
        private int i = 1;  
        private int j;  
    }  
    
    public class InstanceInitializer {  
        private int j = i;  
        private int i = 1;  
    }  
    

    取巧可以绕过上述问题:

    public class InstanceInitializer {  
        private int j = getI();  
        private int i = 1;  
    
        public InstanceInitializer() {  
            i = 2;  
        }  
    
        private int getI() {  
            return i;  
        }  
    
        public static void main(String[] args) {  
            InstanceInitializer ii = new InstanceInitializer();  
            System.out.println(ii.j);  
        }  
    }  
    

    这种方式虽然编译器不报错,但j的结果为0,因为在初始化j时,还没有执行构造函数中的i = 2,因此 i 为内存分配完统一初始化的0值(还没到<init>)

    2)构造函数初始化

    构造函数在编译生成的字节码中,会被命名成<init>()方法,参数列表与Java语言书写的构造函数的参数列表相同。如:

    private int id;
    private String name;
    private String address;
    
    // 构造函数
    public Student(int id, String name) {
        this.id = id;
        this.name = name;
    }
    {
      address = "kk";
    }
    
    // 对应字节码
    // access flags 0x1
      public <init>(ILjava/lang/String;)V
       L0
        LINENUMBER 15 L0
        ALOAD 0
        INVOKESPECIAL java/lang/Object.<init> ()V  // 调用父类构造函数
       L1
        LINENUMBER 20 L1
        ALOAD 0
        LDC "kk"
        PUTFIELD Student.address : Ljava/lang/String; // 将实例代码块织入实例构造器<init>中
       L2
        LINENUMBER 16 L2
        ALOAD 0
        ILOAD 1
        PUTFIELD Student.id : I   // 构造函数中的语句对应
       L3
        LINENUMBER 17 L3
        ALOAD 0
        ALOAD 2
        PUTFIELD Student.name : Ljava/lang/String; // 构造函数中的语句对应
       L4
        LINENUMBER 18 L4
        RETURN
       L5
        LOCALVARIABLE this LStudent; L0 L5 0
        LOCALVARIABLE id I L0 L5 1
        LOCALVARIABLE name Ljava/lang/String; L0 L5 2
        MAXSTACK = 2
        MAXLOCALS = 3
    

    特别地,如果我们在一个构造函数中调用另外一个构造函数,如下所示

    public class ConstructorExample {  
        private int i;  
    
        ConstructorExample() {  
            this(1);  // 调用下面的构造函数
            ....  
        }  
    
        ConstructorExample(int i) {  
            ....  
            this.i = i;  
            ....  
        }  
    }  
    

    对于这种情况,Java只允许在ConstructorExample(int i)内调用超类的构造函数,也就是说,下面两种情形的代码编译是无法通过的:

    public class ConstructorExample {  
        private int i;  
    
        // 1.
        ConstructorExample() {  
            super();  
            this(1);  // Error:Constructor call must be the first statement in a constructor
            ....  
        }  
       // 2.
      ConstructorExample() {  
            this(1);  
            super();  //Error: Constructor call must be the first statement in a constructor
            ....  
        } 
    
        ConstructorExample(int i) {  
            ....  
            this.i = i;  
            ....  
        }  
    }  
    

    总之,java对象的初始化(类的实例化)是一个递归的过程,如下图所示:

    在这里插入图片描述

    参考:https://blog.csdn.net/justloveyou_/article/details/72466416

    展开全文
  • Java 对象的创建过程

    2019-03-21 23:49:55
    Java 对象的创建过程 类的初始化与实例化 一个 Java 对象的创建过程往往包括类的初始化 和 实例化 两个阶段。 Java 规范规定一个对象在可以被使用之前必须要被正确地初始化。在类初始化过程中或初始化完毕后,根据...

    Java 对象的创建过程

    类的初始化与实例化

    一个 Java 对象的创建过程往往包括类的初始化 和 实例化 两个阶段。

    Java 规范规定一个对象在可以被使用之前必须要被正确地初始化。在类初始化过程中或初始化完毕后,根据具体情况才会去对类进行实例化。在实例化一个对象时,JVM 首先会检查相关类型是否已经加载并初始化,如果没有,则 JVM 立即进行加载并调用类构造器完成类的初始化。

    Java 对象的创建方式

    一个对象在可以被使用之前必须要被正确地实例化。在 Java 程序中,有多种方法可以创建对象,最直接的一种就是使用 new 关键字来调用一个类的构造函数显式地创建对象。这种方式是由执行类的实例创建表达式创建对象。除此之外,还可以使用反射机制 (Class 类的 newInstance 方法、Constructor 类的newInstance 方法)、使用 Clone 方法、使用反序列化等方式创建对象。

    1. 使用 new 关键字创建对象
      这是最常见、最简单的创建对象的方式,通过这种方式可以调用任意的构造函数(无参的和有参的)创建对象。
    2. 使用 Class 类的 newInstance 方法 (反射机制) 。事实上 Class 类的 newInstance 方法内部调用的是 Constructor 类的 newInstance 方法,相当于是调用无参的构造器创建对象。
    3. 使用 Constructor 类的 newInstance 方法 (反射机制) 。该方法和 Class 类中的 newInstance 方法类似,不同的是 Constructor 类的 newInstance 方法可以调用有参数的和私有的构造函数。
    4. 使用Clone方法创建对象
      调用一个对象的 clone 方法,JVM 都会创建一个新的、一样的对象。特别需要说明的是,用 clone 方法创建对象的过程中并不会调用任何构造函数。如何使用 clone 方法以及浅克隆/深克隆机制。简单而言,要想使用 clone 方法,就必须先实现 Cloneable 接口并实现其定义的 clone 方法,这也是原型模式的应用。
    5. 使用 (反) 序列化机制创建对象
      当反序列化一个对象时,JVM会创建一个单独的对象,在此过程中,JVM并不会调用任何构造函数。为了反序列化一个对象,对应的类需要实现 Serializable 接口。

    从 Java 虚拟机层面看,除了使用 new 关键字创建对象的方式外,其他方式全部都是通过转变为 invokevirtual 指令直接创建对象的。

    Java 对象的创建过程

    当一个对象被创建时,虚拟机就会为其分配内存来存放对象自己的实例变量及其继承父类的实例变量 (即使继承超类的实例变量有可能被隐藏也会被分配空间) 。在为这些实例变量分配内存的同时,这些实例变量也会被赋予默认值。在内存分配完成之后,Java 虚拟机就会开始对新创建的对象进行初始化。在 Java 对象初始化过程中,主要涉及三种执行对象初始化的结构,分别是实例变量初始化、实例代码块初始化以及构造函数初始化。

    实例变量初始化与实例代码块初始化

    在定义(声明)实例变量的同时,可以直接对实例变量进行赋值或者使用实例代码块对其进行赋值。如果以这两种方式为实例变量进行初始化,那么它们将在构造函数执行之前完成这些初始化操作。实际上,如果对实例变量直接赋值或者使用实例代码块赋值,那么编译器会将其中的代码放到类的构造函数中去,并且这些代码会被放在对超类构造函数的调用语句之后 (构造函数的第一条语句必须是超类构造函数的调用语句) ,构造函数本身的代码之前。

    特别需要注意的是,Java 是按照先后顺序来执行实例变量初始化和实例初始化器中的代码,并且不允许顺序靠前的实例代码块初始化在其后面定义的实例变量。这么做是为了保证一个变量在被使用之前已经被正确地初始化。

    构造函数初始化

    实例变量初始化与实例代码块初始化总是发生在构造函数初始化之前。Java 中的每一个类中都至少会有一个构造函数,如果没有显式定义构造函数,那么 JVM 会为它提供一个默认无参的构造函数。在编译生成的字节码中,这些构造函数会被命名成 () 方法 (参数列表与 Java 语言中构造函数的参数列表相同) 。Java 要求在实例化类之前,必须先实例化其超类,以保证所创建实例的完整性。

    事实上,这一点是在构造函数中保证的:Java 强制要求除 Object 类 (Object 是 Java 的顶层类,没有超类) 之外所有类的构造函数中的第一条语句必须是超类构造函数的调用语句或者是类中定义的其他的构造函数。如果既没有调用其他的构造函数,也没有显式调用超类的构造函数,那么编译器会自动生成一个对超类构造函数的调用。

    如果显式调用超类的构造函数,那么该调用必须放在构造函数所有代码的最前面。正因为如此,Java 才可以使得一个对象在初始化之前其所有的超类都被初始化完成,并保证创建一个完整的对象出来。特别地,如果在一个构造函数中调用另外一个构造函数则不能显式调用超类的构造函数,而且要另一个构造函数放在构造函数所有代码的最前面。

    Java 通过对构造函数作出上述限制保证一个类的实例能够在被使用之前正确地初始化。

    展开全文
  • java对象的创建过程

    千次阅读 2016-03-28 09:27:04
    java对象的创建过程,java的根本,如下图所示:
    

    一、java对象的创建过程,java的根本,如下图所示:


    二、java构造器的顺序(针对有继承的情况)



    展开全文
  • JAVA对象的创建过程

    千次阅读 2019-08-01 18:00:16
    在图中可以清楚看到jvm在创建对象时候执行流程,下面具体讨论一下每个步骤: 在程序进行加载时候,他类信息,常量,静态变量等数据就会存放在方法区(常量池)中。 在执行new操作时候,就会去方法区去...

    今天分析一条指令:

    Demo demo = new Demo();

    这个Demo对象(普通对象)到底是咋来的?

    来看一张图:

    在图中可以清楚的看到jvm在创建对象时候的执行流程,下面具体讨论一下每个步骤:

    在程序进行加载的时候,他的类信息,常量,静态变量等数据就会存放在方法区(常量池)中。

    在执行new操作的时候,就会去方法区去寻找我们相应创建类的信息,如果找到的话执行下一步,

    如果没有找到那么必须先执行类的初始化过程(加载,解析,初始化)

    在类加载检查通过以后,开始为对象分配空间,通常由如下两种方法:

    1.指针碰撞;

         当堆是绝对规整的,所有用过的放在一边,所有没用过的放在一边,中间放着一个指针作为分界的指示器,那么分配内存只需要将指针向空闲区域移动一段与对象同样大小的距离。

    2.空闲列表;

         当堆中是是不规整的,使用的和未使用的内存相互交错,那么就无法进行简单的指针碰撞,这时候需要维护一个表,记录那些内存块是可用的,在分配的时候需要从列表中找出一块足够大的内存块分配给对象实例。

    虚拟机如何分配内存是由堆上是否规整决定的。

    思考一个问题:如果在多线程的情况下,创建对象时非常频繁的操作,那么怎么保证第一种方法的安全?

    先说为什么不安全:当虚拟机正在给A分配内存,还没有来的及修改的时候,又开始给B分配内存,这样就造成了错误。

    那么解决办法有两种:

    1.利用锁的思想。

        即进行同步操作,在分配内存时进行锁定(加标记位),其他线程等待分配结束以后才能区访问。事实上虚拟机采用CAS(自旋)配上失败尝试的方式保证更新的原子性。

    2.把内存分配按照线程分配在不同区域。

       每个线程在java堆中预先分配一块小内存,称为本地线程分配缓存栈(TLAB),哪个线程需要分配内存就在哪个线程的缓冲栈中分配。即设为线程私有的“堆”,

      当自己的缓存栈用完以后,就需要分配新的空间,那么这个时候就需要同步操作,防止出现与上面描述的错误。

      可以通过使用参数 -XX:+/-UseTLAB参数来设定。

     

    内存分配完以后,虚拟机会将分配到的内存空间都置为零值(不包括对象头),这一步保证了对象的实例字段在JAVA中可以不用赋初值就可以直接使用。

    最后就是虚拟机进行必要的配置了,类的元数据信息,哈希码,GC分代年龄等信息。

    这样一个对象就创建完成了,但是在java程序的角度来说,这刚刚开始 ---<init()>方法还没执行。

    展开全文
  • 在语言层面上,创建对象(如克隆、反序列化)通常仅仅是一个new关键字而已,而在虚拟机中对象的创建过程是什么样的呢? 虚拟机遇到一条new指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 14,312
精华内容 5,724
关键字:

java对象的创建过程

java 订阅