到底是怎么实例化的
昨天读到这个帖子 http://www.iteye.com/topic/650911 ·到底JVM实例化一个对象是怎样的了··
到底有没实例化Object···我 说错了大家帮我改正··
先来段简单的程序:
public class Test { public Test(){ System.out.println("默認構造器!!!"); } public static void main(String[] args) { Test test=new Test(); } }
当运行这个程序的时候 JVM底层到底做了些什么,实例化test的时候是怎样的一个过程了??
javap -c Test 反编译这个程序,将会看到如下的指令
如果不熟悉JVM指令,看到这些东西确实难以理解···很直观的看到Test默认为继承自Object这个JAVA中的超级父类,当new Test()的时候,调用Test的默认构造器,构造器其实就是一个特殊的静态的方法(这样说应该没错吧?)·
这些指令到底是在干些什么了???
aload_0 将第一个引用类型本地变量推送至栈顶
invokespecial 调用超类构造方法,实例初始化方法,私有方法(调用Object类的构造方法,JAVA中为每一个类的构造方法都生成了一个实例初始化方法,这个方法被称为<init>,我们在指令中能清晰的看到)
getstatic 获取指定类的静态域,并将其值压入栈顶 (获得System类的PrintStream类型的out静态域)
ldc 将
int
,
float
或String型常量值从常量池中推送至栈顶 (把
"默認構造器!!!"这个字符串推送至栈顶 )
invokevirtual 调用实例方法 (调用 PrintStream类的实例方法println() )
return 从当前方法返回 void
new 创建一个对象,并将其引用值压入栈顶 (test对象)
invokespecial (调用Test构造方法的实例化初始方法<init>)
astore_1
将栈顶引用型数值存入第二个本地变量
return 从当前方法返回 void
类的实例化过程中,如果是多层次的继承的话的顺序是从Object开始,依次向下执行构造函数···
public class Test2 { public Test2(){ System.out.println("我是頂層的構造器"); } }
public class Test1 extends Test2 { public Test1(){ System.out.println("我是2層的構造器"); } }
public class Test extends Test1{ public Test(){ System.out.println("默認構造器!!!"); } public static void main(String[] args) { Test test=new Test(); } }
结果: 我是頂層的構造器 我是2層的構造器 默認構造器!!!
应该并没用实例话Object吧 只是调用了构造方法吧··正如我上面说的···构造器其实就是一个特殊的静态的方法···
并不要创建实例对象·就可以执行·····
这个也许是我相当然吧····最近在读深入JAVA虚拟机··等我读的有点头绪了 ·再来下次说说这个吧· 只是最进看这虚拟机的书,看得满投雾水··看来是我自己太菜了···
也是很晕
你这样测试 是因为你自己写了 Test的构造器
有输出
主要是要多看几遍。。。
你还没怎么看完这本书,书上有写的。。。当实例化Test类的时候,实例化方法<init>()会调用父类的实例化方法。。所以父类总是在子类实例化之前实例化。。。
lz工作几年了?
我是来歪楼的,专门顶顶你,哈哈
你这样测试 是因为你自己写了 Test的构造器
有输出
我只是想更形象啊··
主要是要多看几遍。。。
你还没怎么看完这本书,书上有写的。。。当实例化Test类的时候,实例化方法<init>()会调用父类的实例化方法。。所以父类总是在子类实例化之前实例化。。。
lz工作几年了?
这个话 我好像没写吧????
我啊 工作不到一年啊 菜鸟一个啊 ··莫见笑啊 ··
1.加载类,因为接下来的过程需要知道诸如类大小,类成员结构等信息.
2.在堆上分配一块内存. 类的大小在第一步可以知道,类有多大,此分配的内存一般就多大(先忽略掉数据对齐,virtual table等存在).
3.执行构造函数, 此构造函数一般是经过编译器拼凑的,除了用户自定义的初始化,还包含隐式地对父构造函数的调用,聚合对象成员构造器的调用,virtual table的初始化等....
4.留下一些元信息,让jvm知道你曾经实例化过一个类....
个人理解,说地粒度也粗,随便看看~~
1.加载类,因为接下来的过程需要知道诸如类大小,类成员结构等信息.
2.在堆上分配一块内存. 类的大小在第一步可以知道,类有多大,此分配的内存一般就多大(先忽略掉数据对齐,virtual table等存在).
3.执行构造函数, 此构造函数一般是经过编译器拼凑的,除了用户自定义的初始化,还包含隐式地对父构造函数的调用,聚合对象成员构造器的调用,virtual table的初始化等....
4.留下一些元信息,让jvm知道你曾经实例化过一个类....
个人理解,说地粒度也粗,随便看看~~
virtual table的初始化可不是在构造函数中进行,而是在加载类的过程中完成。
1.加载类,因为接下来的过程需要知道诸如类大小,类成员结构等信息.
2.在堆上分配一块内存. 类的大小在第一步可以知道,类有多大,此分配的内存一般就多大(先忽略掉数据对齐,virtual table等存在).
3.执行构造函数, 此构造函数一般是经过编译器拼凑的,除了用户自定义的初始化,还包含隐式地对父构造函数的调用,聚合对象成员构造器的调用,virtual table的初始化等....
4.留下一些元信息,让jvm知道你曾经实例化过一个类....
个人理解,说地粒度也粗,随便看看~~
virtual table的初始化可不是在构造函数中进行,而是在加载类的过程中完成。
嗯,在JAVA中,vt的内容的确应该是在类加载,甚至应该说更早的编译阶段就可以确定了.
但具体用哪个类的vt,要延迟到实例化的过程中(构造函数)才能确定。
1.加载类,因为接下来的过程需要知道诸如类大小,类成员结构等信息.
2.在堆上分配一块内存. 类的大小在第一步可以知道,类有多大,此分配的内存一般就多大(先忽略掉数据对齐,virtual table等存在).
3.执行构造函数, 此构造函数一般是经过编译器拼凑的,除了用户自定义的初始化,还包含隐式地对父构造函数的调用,聚合对象成员构造器的调用,virtual table的初始化等....
4.留下一些元信息,让jvm知道你曾经实例化过一个类....
个人理解,说地粒度也粗,随便看看~~
virtual table的初始化可不是在构造函数中进行,而是在加载类的过程中完成。
嗯,在JAVA中,vt的内容的确应该是在类加载,甚至应该说更早的编译阶段就可以确定了.
但具体用哪个类的vt,要延迟到实例化的过程中(构造函数)才能确定。
不妨在bytecode一级上来理解, 一个new String("hello")对应于如下的字节码:
# new java.lang.String
# dup
# invokespecial java.lang.String.<init>(java.lang.String)
因此,实例化实际上包括两部分:
# new 对应于分配对象内存
# 调用构造函数。
其实,new 这个操作是非常简单的,基本上就两个操作:
1、malloc(sizeof instance)
2. instance.classPtr = class(在Hotspot中 每个对象有一个2字的头,其中一个字就包括对class的引用)
实际上,vtable是对象class的一部分。因此,我觉得,具体要使用那个类的vt,实际上是不需要延迟的,使用的就是这个class的vt。
这就等于在构造函数调用的时候才把当前实例的vt指针指向具体class里的vt表.
vt的构造不需要延迟,但vt的选定肯定需要延迟,要不然多态为什么还要叫动态绑定?