目录
原理说明
接着上一篇。
1.被编译成helloworld.class文件进入到虚拟机的方法区中。
2.执行Main方法把Main方法压到线程栈里面
3.在线程栈中会声明一个对象。假如Main方法中的代码是 Hello hello = new Hello();这一步是在线程栈中声明了一个Hello hello对象。
4.在堆内存中申请一片内存地址,然后将Hello相关信息,new Hello();这些实例变量和实例方法从方法区加入到堆内存。
5.对象的声明指向堆内存中开辟的对象。即给Hello hello 和 new Hello();中间赋了一个等号。
堆内存中开辟的对象结构
对象由对象的头部信息和实例信息组成。我们这里主要了解头部信息,其中包括。
1.对齐填充。指的是开辟对象的大小。
2.持有指向方法区的指针。指对内存和方法区的通道。
3.描述信息。(持有当前对象锁的线程id和持有对象锁线程的个数,在gc中存活的生命周期数,偏向锁的标志)
偏向锁指当线程已经对此对象上锁后,执行完毕如果下一次访问该对象的线程也是上一次的线程那么不对此线程重新上锁。
方法的执行实例
简单的小呆毛
public class Demo {
static {
System.out.println("静态代码块");
}
{
System.out.println("普通代码块");
}
public Demo() {
System.out.println("构造方法");
}
public static void main(String[] args) {
new Demo();
}
}
下面来讲解下代码的执行顺序。
1.静态代码块先执行:类加载器在其第五个步骤初始化时,把我们定义的static变量或static静态代码块按顺序组成成了类构造器来初始化变量。
2.普通代码块和构造方法的执行都是在使用这一步骤。普通代码块要先于构造方法,因为使用时先加载实例信息进入开辟的内存空间然后再进行构造。
3.构造方法也是在使用这一步骤被执行,它的执行就是在执行<init>方法
父子类的执行顺序
把上面的Demo简单改动了一下
package demo;
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 Demo {
public static void main(String[] args) {
new Son();
}
}
下面来讲解下代码的执行顺序。
1.main方法进入类加载器后,被发现里面的代码是new Son,于是Son进入到类加载器。当Son进入到类加载器时,类加载器发现Son继承了一个Father类。于是在初始化之前,先让Father类进入到了类加载器中。接着类加载器先初始化的是Father的静态代码块。
2.初始化完Father的静态代码块。这个阶段依然没有结束,紧接着是初始化Son的静态代码块。
3.接着走到了使用阶段。使用时时先执行普通方法,再执行构造方法。由于检测到Son有Father所以在这个阶段先把Father的方法执行完,再执行Son的方法。所以顺序依次是Father普通代码块,Father的构造方法,Son的普通代码块,Son的构造方法。