JVM--对象的实例化过程
Java对象创建时机
一个对象在可以被使用之前必须要被正确地实例化。在Java代码中,有很多行为可以引起对象的创建。下面对各种方式一一介绍。
使用new关键字创建对象
这是我们最常见的也是最简单的创建对象的方式,通过这种方式我们可以调用任意的构造函数(无参的和有参的)去创建对象。比如:
Student student = new Student();
使用Class类的newInstance方法(反射机制)
我们也可以通过Java的反射机制使用Class类的newInstance方法来创建对象,事实上,这个newInstance方法调用无参的构造器创建对象,比如:
Student student2 = (Student)Class.forName("Student类全限定名").newInstance();
或者:
Student stu = Student.class.newInstance();
使用Constructor类的newInstance方法(反射机制)
java.lang.relect.Constructor类里也有一个newInstance方法可以创建对象,该方法和Class类中的newInstance方法很像,但是相比之下,Constructor类的newInstance方法更加强大些,我们可以通过这个newInstance方法调用有参数的和私有的构造函数,比如:
public class Student {private int id;public Student(Integer id) {this.id = id;}
}public class MainStart {public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {Constructor constructor = Student.class.getDeclaredConstructor(String.class);//flag的值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查。constructor.setAccessible(true);Student student = constructor.newInstance(666);}
}
使用(反)序列化机制创建对象
当我们反序列化一个对象时,JVM会给我们创建一个单独的对象,在此过程中,JVM并不会调用任何构造函数。为了反序列化一个对象,需要让类实现Serializable接口。
Java 对象的创建过程
当一个对象被创建时,虚拟机就会为其分配内存来存放对象自己的实例变量及其从父类继承过来的实例变量(即使这些从超类继承过来的实例变量有可能被隐藏也会被分配空间)。在为这些实例变量分配内存的同时,这些实例变量也会被赋予默认值(零值)。在内存分配完成之后,Java虚拟机就会开始对新创建的对象按照程序猿的意志进行初始化。在Java对象初始化过程中,主要涉及三种执行对象初始化的结构:
- 实例变量初始化、实例代码块初始化
- 构造函数初始化。
实例变量初始化
定义实例变量的同时,还可以直接对实例变量进行赋值或者使用实例代码块对其进行赋值。如果我们以这两种方式为实例变量进行初始化,那么它们将在构造函数执行之前完成这些初始化操作。实际上,如果我们对实例变量直接赋值或者使用实例代码块赋值,那么编译器会将其中的代码放到类的构造函数中去,并且这些代码会被放在对超类构造函数的调用语句之后,构造函数本身的代码之前。例如:
public class ObjectTest {private int n = 100;private int m ;public ObjectTest(){super();//实例变量直接赋值或者使用实例代码块赋值,会被编译器放到这里System.out.println(m);}{ // 实例代码块m = n*2;}public static void main(String[] args) {new ObjectTest();}
}
特别需要注意的是,Java是按照编程顺序来执行实例变量初始化器和实例初始化器中的代码的,并且不允许顺序靠前的实例代码块初始化在其后面定义的实例变量,即变量使用前必先定义。
构造函数初始化
每一个Java中的对象都至少会有一个构造函数,如果我们没有显式定义构造函数,那么它将会有一个默认无参的构造函数。在编译生成的字节码中,这些构造函数会被命名成()方法,参数列表与Java语言书写的构造函数的参数列表相同。
Java要求在实例化类之前,必须先实例化其超类,以保证所创建实例的完整性。事实上,这一点是在构造函数中保证的:Java强制要求Object对象(Object是Java的顶层对象,没有超类)之外的所有对象构造函数的第一条语句必须是超类构造函数的调用语句或者是类中定义的其他的构造函数,如果我们既没有调用其他的构造函数,也没有显式调用超类的构造函数,那么编译器会为我们自动生成一个对超类构造函数的调用,比如一个无参构造函数:
public Test01() {
}
编译之后构造方法的字节码为:
public <init>()V
L0
LINENUMBER 4 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
L1
LINENUMBER 6 L1
RETURN
L2
LOCALVARIABLE this Lobjecttest/TestSuper; L0 L2 0
MAXSTACK = 1
MAXLOCALS = 1
上面代码INVOKESPECIAL java/lang/Object. ()V就是调用Object类的默认构造函数的指令。也就是说,如果我们显式调用超类的构造函数,那么该调用必须放在构造函数所有代码的最前面,也就是必须是构造函数的第一条指令。正因为如此,Java才可以使得一个对象在初始化之前其所有的超类都被初始化完成,并保证创建一个完整的对象出来。
如果在构造方法1中调用构造方法2,构造方法1必须在第一行调用,否则不能通过编译,而对超类的构造方法的调用实在内层,也就是super方法是在构造方法2中调用。如下:
public Test02() {}
public Test02(int n) {this();this.n = n;}
编译后的字节码为:
public <init>()V
L0
LINENUMBER 6 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
L1
LINENUMBER 8 L1
RETURN
L2
LOCALVARIABLE this Lobjecttest/Test02; L0 L2 0
MAXSTACK = 1
MAXLOCALS = 1// access flags 0x1
public <init>(I)V
L0
LINENUMBER 11 L0
ALOAD 0
INVOKESPECIAL objecttest/Test02.<init> ()V
L1
LINENUMBER 12 L1
ALOAD 0
ILOAD 1
PUTFIELD objecttest/Test02.n : I
L2
LINENUMBER 14 L2
RETURN
L3
LOCALVARIABLE this Lobjecttest/Test02; L0 L3 0
LOCALVARIABLE n I L0 L3 1
MAXSTACK = 2
MAXLOCALS = 2
小结
在准备实例化一个类的对象前,首先准备实例化该类的父类,如果该类的父类还有父类,那么准备实例化该类的父类的父类,依次递归直到递归到Object类。此时,首先实例化Object类,再依次对以下各类进行实例化,直到完成对目标类的实例化。具体而言,在实例化每个类时,都遵循如下顺序:先依次执行实例变量初始化和实例代码块初始化,再执行构造函数初始化。也就是说,编译器会将实例变量初始化和实例代码块初始化相关代码放到类的构造函数中去,并且这些代码会被放在对超类构造函数的调用语句之后,构造函数本身的代码之前。
几个对象初始化的问题
一个实例变量在对象初始化的过程中会被赋值几次?
JVM在为一个对象分配完内存之后,会给每一个实例变量赋予默认值,这个时候实例变量被第一次赋值,这个赋值过程是没有办法避免的。如果我们在声明实例变量x的同时对其进行了赋值操作,那么这个时候,这个实例变量就被第二次赋值了。如果我们在实例代码块中,又对变量x做了初始化操作,那么这个时候,这个实例变量就被第三次赋值了。如果我们在构造函数中,也对变量x做了初始化操作,那么这个时候,变量x就被第四次赋值。也就是说,在Java的对象初始化过程中,一个实例变量最多可以被初始化4次。
下面代码的执行结果是怎样?
public class StaticTest {int a = 1; // 实例变量static StaticTest STATIC = new StaticTest();static { //静态代码块System.out.println("static block");}static int B = 2; // 静态变量{ // 实例代码块System.out.println("code block");}StaticTest() { // 实例构造器System.out.println("constract run");System.out.println("a=" + a + ",B=" + b);}public static void staticFunction() { // 静态方法System.out.println("static function");}public static void main(String[] args) {staticFunction();}
}
输出结果:
code block
constract run
a=1,b=2
static block
static function
首先明确一个问题就是
- 实例初始化不一定要在类初始化结束之后才开始初始化。
- 类的生命周期是:加载->验证->准备->解析->初始化->使用->卸载,并且只有在准备阶段和初始化阶段才会涉及类变量的初始化和赋值
下面逐步分析:
- 因为在main方法调用了静态变量开始类加载,在类的准备阶段需要做的是为类变量(static变量)分配内存并设置默认值(零值),因此在该阶段结束后,类变量STATIC将变为null、B变为0。
- 在类的初始化阶段需要做的是执行类构造器(),需要指出的是,类构造器本质上是编译器收集所有静态语句块和类变量的赋值语句按语句在源码中的顺序合并生成类构造器()。因此,对上述程序而言,JVM将先执行第一条静态变量的赋值语句,然后再给静态变量b赋值。
- 最核心的点来了:在同一个类加载器下,一个类型只会被初始化一次。所以,一旦开始初始化一个类型,无论是否完成,后续都不会再重新触发该类型的初始化阶段了(只考虑在同一个类加载器下的情形)。因此,在实例化上述程序中的STATIC变量时(
static StaticTest STATIC = new StaticTest();),实际上是把实例初始化嵌入到了静态变量初始化流程中,并且在上面的程序中,嵌入到了静态初始化的起始位置。这就导致了实例初始化完全发生在静态代码块初始化之前,当然,这也是导致a为1,B为0的原因。 - 接下来开始对象初始化,父类构造函数>实例变量的赋值>代码块的执行>构造函数
- 至此static StaticTest STATIC静态赋值完成,接下里继续类的初始化,按照源码的顺序执行静态代码块,静态变量B的赋值,直到类的初始化完成。
顺序总结:
类加载准备阶段(类变量的设置默认值)--> 静态语句块与类变量赋值语句 --> 父类构造函数 --> 实例变量赋值与实例代码块 --> 构造函数。
来源于:https://blog.csdn.net/TheLudlows/article/details/82561596
JVM--对象的实例化过程相关推荐
- Java对象的实例化过程是怎样的?
勇气 对象实例化过程 底层实现原理 检查符号引用 分配内存 线程安全处理 初始化内存 设置对象信息 执行init构造 代码示例 对象实例化过程 对象实例化过程,就是执行类构造函数对应在字节码文件中的 ...
- java基础代码实例_基础篇:详解JAVA对象实例化过程
1 对象的实例化过程 对象的实例化过程是分成两部分:类的加载初始化,对象的初始化 要创建类的对象实例需要先加载并初始化该类,main方法所在的类需要先加载和初始化 类初始化就是执行方法,对象实例化是执 ...
- 详细完整的说说对象实例化过程
对象的实例化过程需要做哪些工作呢?首先 Java 是一门面向对象的语言,类是对所属于一类的所有对象的抽象,对象的所有结构化信息都定义在了类中,因此对象的创建需要根据类中定义的类型信息,也就是类所对应的 ...
- 说说 Spring Bean 的实例化过程?面试必问
文章目录 1.Spring实例化Bean的几种方式 1.1 构造器方式(基于反射实现) 1.2 静态工厂方式(factory-method) 1.3 使用实例工厂方法实例化Bean(@Bean) 1. ...
- 面试官必问:说说 Spring Bean 的实例化过程?
不贴代码,Spring的Bean实例化过程应该是怎样的? 两个阶段 容器启动阶段 Bean实例化阶段 不贴代码,Spring的Bean实例化过程应该是怎样的? 对于写Java的程序员来说,Spring ...
- 从JVM看类的加载过程与对象实例化过程
一. 类的加载过程 1. 类的加载过程大致是个什么过程? 我们编写产生.java文件,这些.java文件经过Java编译器编译成拓展名为.class的文件,.class文件中保存着Java代码经转换后 ...
- java初始化实例化_Java对象的创建过程:类的初始化与实例化
一.Java对象创建时机 我们知道,一个对象在可以被使用之前必须要被正确地实例化.在Java代码中,有很多行为可以引起对象的创建,最为直观的一种就是使用new关键字来调用一个类的构造函数显式地创建对象 ...
- JVM:对象的实例化、内存布局与访问定位
对象的实例化 创建对象的方式 new关键字 最常见的方式 变形1:Xxx的静态方法(单例模式) 变形2:XxxBuilder/XxxFactory的静态方法 Class的newInstance() 反 ...
- Java对象的创建过程:类的初始化与实例化
一.Java对象创建时机 我们知道,一个对象在可以被使用之前必须要被正确地实例化.在Java代码中,有很多行为可以引起对象的创建,最为直观的一种就是使用new关键字来调用一个类的构造函数显式地创建对象 ...
- java实例化类之后如何赋值_深入理解Java对象的创建过程:类的初始化与实例化...
摘要: 在Java中,一个对象在可以被使用之前必须要被正确地初始化,这一点是Java规范规定的.在实例化一个对象时,JVM首先会检查相关类型是否已经加载并初始化,如果没有,则JVM立即进行加载并调用类 ...
最新文章
- Angular 2 Pipe
- 算法2:判断两个字符串内容是否相同
- golang中的类和接口的使用
- c语言统计数据,数据统计
- Java 多线程异常捕获Runnable实现
- codefroces 297E Mystic Carvings
- 天猫精灵方糖拆解报告和芯片详解
- Datawhale 人工智能培养方案
- 年轻人开始“反算法”
- TensorFlow windows之Tensorboard使用
- 模块化无人机,不仅配置高,还颜值爆表
- Bailian2998 日志排序【排序】
- 用递归方法求一个list的最大值
- ubuntu14.04/Mint17上Gitolite的搭建过程
- 影响因子在10的计算机杂志什么水平,国人无人发表的TOP期刊,影响因子翻倍,5分变10分,不收版面费!...
- pytorch搭建分类网络并进行训练和测试
- 金融类自定义View(二)--股票分时图
- 【概率论与数理统计(研究生课程)】知识点总结6(抽样分布)
- 变种水仙花数 - Lily Number
- linux 禁用usb驱动程序,如何使用musb_hdrc Linux驱动程序断开特定的USB设备?