继续Hello World
public class Hello{
public static void main(String[] agrs){
Hello hello = new Hello();
}
}
类名,类中的方法,都在方法区中。执行时会进行压栈,当执行到new关键字时
对象的创建过程:
1、那么main主线程便在自己的线程栈中声明了一个对象。
Hello hello;
对象的声明;
new Hello();
创建一个对象
2、在JVM的堆内存空间中申请一片内存,然后将Hello类相关信息如:实例变量,方法等从方法区中加载到堆内存中。
3、Hello hello = new Hello(); ‘=’的意思,对象的声明也就是引用指向堆内存中开辟的对象。
总结:首先在线程栈中创建对象,然后会在堆内存开辟出一块内存出来,将对象引用给堆内存开辟出来的对象。
*2、在JVM的堆内存空间中申请一片内存地址,然后将Hello类相关信息如:实例变量,方法等从方法区中加载到堆内存中。
在堆内存中创建对象的时候执行顺序:
(1)、加载实例信息进入开辟的内存中。
(2)、执行构造方法就是<init>方法。
对堆内存中开辟的对象的结构进行讲解:
对象由对象的头部信息和实例的信息组成。
头部信息:
1、对齐填充。
2、持有指向方法区的指针。
3、描述信息(持有当前对象锁的线程的id和持有对象锁线程的个数、在gc中存活的生命周期数、偏向锁的标志)
偏向锁:当线程已经对此对象上锁后,执行完毕如果下一次访问该对象的线程也是上一次的线程那么不对此线程
重新上锁。
public class Demo01 {
static{
System.out.println("静态代码块");
}
{
System.out.println("普通代码块");
}
public Demo01(){
System.out.println("构造代码块");
}
public static void main(String[] args) {
new Demo01();
}
}
此代码执行顺序 静态 -> 普通 -> 构造 为什么呢?
第一步,通过javac 变成 Demo01.class文件。
第二步:Demo01.class文件,加载进入JVM。
第三步:验证类信息,魔术值,JVM版本,编译class的版本,验证字节码,合格之后进入下一步。
第四步:准备,初始化类成员变量。public static (final)
静态代码块在类初始化的时候,static已经被执行,所以优先执行静态代码块。
普通代码块在 加载实例信息进入开辟的内存中 的时候被使用。
当堆内存中实例信息被加载完毕之后,会自动执行构造方法。
案例2:
class Father{
static{
System.out.println("Father静态代码块");
}
{
System.out.println("Father普通代码块");
}
public Father(){
System.out.println("Father构造代码块");
}
}
class Son extends Father{
static{
System.out.println("Son静态代码块");
}
{
System.out.println("Son普通代码块");
}
public Son(){
System.out.println("Son构造代码块");
}
}
public class Demo01 {
static{
System.out.println("静态代码块");
}
{
System.out.println("普通代码块");
}
public Demo01(){
System.out.println("构造代码块");
}
public static void main(String[] args) {
new Son();
}
}
代码执行顺序:
Father静态代码块
Son静态代码块
Father普通代码块
Father构造代码块
Son普通代码块
Son构造代码块
为什么是这样的执行顺序?
当new Son()执行时,在初始化之前,会检查到有父类,优先执行初始化父类。
父类初始化完毕之后,会初始化子类,执行子类static 代码块。
随后进入父类,加载实例信息,也就是普通代码块。
随后父类的构造器代码块。
父类执行结束,执行子类的普通代码块,和子类的构造器代码块。
另外需要注意的是:子类构造器中就算在开发过程中不写super(); 在编译的时候,也会自动在子类构造器中加上super();
父类构造器代码块执行完毕,才会执行子类的构造器代码块。