【Java基础】Java类的加载和对象创建流程的详细分析

相信我们在面试Java的时候总会有一些公司要做笔试题目的,而Java类的加载和对象创建流程的知识点也是常见的题目之一。接下来通过实例详细的分析一下。

实例问题

实例代码

Parent类

 1 package mytest.javaBase;
 2
 3 public class Parent {
 4     int a = 10;
 5     static int b = 11;
 6     // 静态代码块
 7     static {
 8         System.out.println("Parent静态代码块:b=" + b);
 9         b++;
10     }
11     // 代码块
12     {
13         System.out.println("Parent代码块: a=" + a);
14         System.out.println("Parent代码块: b=" + b);
15         b++;
16         a++;
17     }
18
19     // 无参构造函数
20     Parent() {
21         System.out.println("Parent无参构造函数: a=" + a);
22         System.out.println("Parent无参构造函数: b=" + b);
23     }
24
25     // 有参构造函数
26     Parent(int a) {
27         System.out.println("Parent有参构造函数: a=" + a);
28         System.out.println("Parent有参构造函数: b=" + b);
29     }
30
31     // 方法
32     void function() {
33         System.out.println("Parent function run ……");
34     }
35
36 }

Child类

 1 package mytest.javaBase;
 2
 3 public class Child extends Parent {
 4     int x = 10;
 5     static int y = 11;
 6     // 静态代码块
 7     static {
 8         System.out.println("Child静态代码块:y=" + y);
 9         y++;
10     }
11     // 代码块
12     {
13         System.out.println("Child代码块: x=" + x);
14         System.out.println("Child代码块: y=" + y);
15         y++;
16         x++;
17     }
18
19     // 构造函数
20     Child() {
21         System.out.println("Child构造函数: x=" + x);
22         System.out.println("Child构造函数: y=" + y);
23     }
24
25     // 方法
26     void function() {
27         System.out.println("Child function run ……");
28     }
29
30 }

Test测试类

 1 package mytest.javaBase;
 2
 3 public class Test {
 4     public static void main(String[] args) {
 5         Child demo = new Child();
 6         demo.function();
 7         System.out.println("…………………………………………………………………………………………………………………………");
 8         Child child = new Child();
 9         child.function();
10     }
11 }

我们可以先不看运行结果,自己思考下,运行结果会是什么,之后再比较下和自己思考的结果是否一样。

运行结果


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21


Parent静态代码块:b=11

Child静态代码块:y=11

Parent代码块: a=10

Parent代码块: b=12

Parent无参构造函数: a=11

Parent无参构造函数: b=13

Child代码块: x=10

Child代码块: y=12

Child构造函数: x=11

Child构造函数: y=13

Child function run ……

…………………………………………………………………………………………………………………………

Parent代码块: a=10

Parent代码块: b=13

Parent无参构造函数: a=11

Parent无参构造函数: b=14

Child代码块: x=10

Child代码块: y=13

Child构造函数: x=11

Child构造函数: y=14

Child function run …… 

结果详细分析

我们运行Test类的main方法

1、  启动JVM,开始分配内存空间;

2、  开始加载Test.class文件,加载到方法区中,在加载的过程中静态的内容要进入静态区中;

3、  在开始运行main方法,这时JVM就会把main调用到栈中运行,开始从方法的第一行往下运行;

4、  在main方法中new Child();这时JVM就会在方法区中查找有没有Child文件,如果没有就加载Child.class文件,并且Child继承Parent类,所以也要查找有没有Parent类,如果没有也要加载Parent.class文件。

5、  Child.class和Parent.class中的所有的非静态内容会加载到非静态的区域中,而静态的内容会加载到静态区中。在加载静态的过程中,先要加载静态的成员变量,然后再加载静态代码块,之后再加载静态的成员方法。

说明:类的加载只会执行一次。下次再创建对象时,可以直接在方法区中获取class信息。

6、  开始给静态区中的所有静态的成员变量开始默认初始化。默认初始化完成之后,开始给所有的静态成员变量显示初始化。

7、  所有静态成员变量显示初始化完成之后,开始执行静态的代码块。先执行父类的静态代码块,再执行子类的静态代码块。


1

2

3


//这时输出

Parent静态代码块:b=11

Child静态代码块:y=11

说明:>>静态代码块是在类加载的时候执行的,类的加载只会执行一次所以静态代码块也只会执行一次;

>>非静态代码块和构造函数中的代码是在对象创建的时候执行的,因此对象创建(new)一次,它们就会执行一次。

8、  这时Parent.class文件 和 Child.class文件加载完成。

9、  开始在堆中创建Child对象。给Child对象分配内存空间,其实就是分配内存地址。

10、开始对类中的的非静态的成员变量开始默认初始化。

11、开始加载对应的构造方法,执行隐式三步

①有个隐式的super(); 

②显示初始化(给所有的非静态的成员变量)

③执行构造代码块

之后才开始执行本类的构造方法中的代码

super()是调用父类的构造函数,此处即为Parent的构造函数,在Parent的构造函数中也有个隐式三步:首先super(),再执行Parent的显示初始化,然后执行Parent的非静态构造代码块,最后执行Parent的构造函数中的代码。


1

2

3

4

5


//这时输出

Parent代码块: a=10

Parent代码块: b=12

Parent无参构造函数: a=11

Parent无参构造函数: b=13 

说明:虽然Parent没有明写extends,但是我们知道在Java中有个超类Object,它是所有类的父类,因此此处Parent类的super()是调用Object的构造函数

Parent的执行完之后,回来继续执行Child自己的隐式三步中的第二步:显示初始化,然后执行Child的非静态代码块的,最后执行Child的构造函数中的代码


1

2

3

4

5


//这时输出

Child代码块: x=10

Child代码块: y=12

Child构造函数: x=11

Child构造函数: y=13

12、对象创建完成,把内存的地址赋值给demo使用。

13、执行demo.function()方法。


1

2


//这时输出

Child function run ……

14、由于后面又创建(new)了一个新的Child对象,因此重复一下【9】之后的步骤,很容易明白它的输出结果为


1

2

3

4

5

6

7

8

9


Parent代码块: a=10

Parent代码块: b=13

Parent无参构造函数: a=11

Parent无参构造函数: b=14

Child代码块: x=10

Child代码块: y=13

Child构造函数: x=11

Child构造函数: y=14

Child function run ……

简单的画个内存运行示例图

总结

我们知道,我们在创建(new)一个对象的时候,先要去JVM的方法区里获取该对象所对应的类的信息,如果方法区里没有该类的信息,则需要去将它加载进来,加载进来之后,有了该类的信息,我们才能创建一个对象。

一般,Java类被编译后,会生成一个class文件,在运行的时候会将class文件加载到Java虚拟机JVM中,class文件由类装载器装载,在JVM中(准确的来说应该是在JVM的方法区里)将形成一份描述Class结构的元信息对象,通过该元信息对象可以获知Class的结构信息:如构造函数,属性和方法等。

一、类的加载过程

首先,Jvm在执行时,遇到一个新的类时,会到内存中的方法区去找class的信息,如果找到就直接拿来用,如果没有找到,就会去将类文件加载到方法区。在类加载时,静态成员变量加载到方法区的静态区域,非静态成员变量加载到方法区的非静态区域。

静态代码块是在类加载时自动执行的代码,非静态代码块是在创建对象时自动执行的代码,不创建对象不执行该类的非静态代码块。

顺序: 静态代码块--》非静态代码块--》类构造方法。

加载过程:

1、JVM会先去方法区中找有没有相应类的.class存在。如果有,就直接使用;如果没有,则把相关类的.clss加载到方法区。

2、在.class加载到方法区时,先加载父类再加载子类;先加载静态内容,再加载非静态内容

3、加载静态内容:

  • 把.class中的所有静态内容加载到方法区下的静态区域内
  • 静态内容加载完成之后,对所有的静态变量进行默认初始化
  • 所有的静态变量默认初始化完成之后,再进行显式初始化
  • 当静态区域下的所有静态变量显式初始化完后,执行静态代码块

4、加载非静态内容:把.class中的所有非静态变量及非静态代码块加载到方法区下的非静态区域内。

5、执行完之后,整个类的加载就完成了。

对于静态方法和非静态方法都是被动调用,即系统不会自动调用执行,所以用户没有调用时都不执行,主要区别在于静态方法可以直接用类名直接调用(实例化对象也可以),而非静态方法只能先实例化对象后才能调用。

二、对象的创建过程

1、new一个对象时,在堆内存中开辟一块空间。

2、给开辟的空间分配一个地址。

3、把对象的所有非静态成员加载到所开辟的空间下。

4、所有的非静态成员加载完成之后,对所有非静态成员变量进行默认初始化。

5、所有非静态成员变量默认初始化完成之后,调用构造函数。

6、在构造函数入栈执行时,分为两部分:先执行构造函数中的隐式三步,再执行构造函数中书写的代码。

隐式三步:
   ①执行super()语句

   ②显示初始化(对开辟空间下的所有非静态成员变量进行) 

③执行构造代码块

7、在整个构造函数执行完并弹栈后,把空间分配的地址赋给引用对象。

三、其他

super语句,可能出现以下三种情况:

1)构造方法体的第一行是this语句,则不会执行隐式三步,而是调用this语句所对应的的构造方法,最终肯定会有第一行不是this语句的构造方法。


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23


package mytest.javaBase;

public class Student {

    private String name;

    private String age;

    Student() {

    };

    Student(String name) {

        this.name = name;

    };

    Student(String name, String age) {

        // 不会执行隐式三步

        this(name);

        this.age = age;

    };

}

2)构造方法体的第一行是super语句,则调用相应的父类的构造方法, 
3)构造方法体的第一行既不是this语句也不是super语句,则隐式调用super(),即其父类的默认构造方法,这也是为什么一个父类通常要提供默认构造方法的原因;

更多内容:http://www.cnblogs.com/study-everyday/

时间: 2024-12-08 14:42:56

【Java基础】Java类的加载和对象创建流程的详细分析的相关文章

Java类的加载和对象创建流程的详细分析

相信我们在面试Java的时候总会有一些公司要做笔试题目的,而Java类的加载和对象创建流程的知识点也是常见的题目之一.接下来通过实例详细的分析一下: package com.test; public class Parent { int a = 10; static int b =11; //静态代码块 static { System.out.println("parent静态代码块:b=" + b); b++; } //代码块 { System.out.println("P

深入java虚拟机学习 -- 类的加载机制(续)

昨晚写 深入java虚拟机学习 -- 类的加载机制 都到1点半了,由于第二天还要工作,没有将上篇文章中的demo讲解写出来,今天抽时间补上昨晚的例子讲解. 这里我先把昨天的两份代码贴过来,重新看下: class Singleton { private static Singleton singleton = new Singleton(); //第一份代码的位置 public static int counter1; public static int counter2=0; private s

java中的类的加载、连接、初始化

同一类的所有实例的静态变量共享用一块内存区. 但两个jvm之间并不会共享数据.类被加载之后,系统为之生成一个对应的class对象. 1.类的加载 将类的class文件读入内存,并创建class对象. 2.连接 连接的过程分为三步: (1)验证 被加载的类的内部结构是否正确.协调. (2)准备 为类变量分配内存并设置默认初始值. (3)解析 将类的二进制数据中的符号引用替换成直接引用. 3.初始化 虚拟机负责对类进行初始化,主要对类变量进行初始化. 1.声明类变量时指定初始值. 2.使用静态初始化

java 反射,类的加载过程以及Classloader类加载器

首先自定义一个类Person package reflection; public class Person { private String name; public int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int a

【深入理解Java虚拟机 】类的加载器

1. 类加载器的分类 JVM 自带的类加载器 根类加载器( BootStrap ) 拓展类加载器 ( Extension ) 系统 (应用) 加载器 ( System / AppClassLoader) 开发者自己创建的类加载器 java.long.ClassLoader 的子类 public abstract class ClassLoader { // 抽象类,不能实例化吗,需要继承并重写其方法 } 2. 加载时机 类加载器不是在 "首次主动" 使用的时候采取尝试加载一般情况下回提

类的加载的一个小问题

前言 之前写了一篇文章专门介绍了一下类的加载和对象的创建流程,然后收到了一个博友的疑问,觉得蛮好的,在这里和大家分享下. 博文地址:[Java基础]Java类的加载和对象创建流程的分析 疑问 1 public class Test3 { 2 public static Test3 t = new Test3(); 3 4 { 5 System.out.println("blockA"); 6 } 7 8 static { 9 System.out.println("block

类的加载

当调用java命令运行某个java程序时,该命令将会启动一个java虚拟机,不管该程序有多么复杂,启动了多少个线程,他们都处于该java虚拟机里,同一个JVM的所有线程,所有变量都处于同一个进程里,都使用该JVM进程的内存区 以下情况JVM被终止: 程序运行到正常结束 程序运行到System.exit()或Runtime.getRuntime().exit()代码处结束程序 程序执行过程中遇到未捕获的异常或者错误 程序所在平台强制结束了JVM进程 JVM进程结束,该进程在内存中的状态将会丢失 系

java 类的加载、连接和初始化

JVM和类 调用Java命令运行Java程序时,该命令将会启动一条Java虚拟机进程,不管该Java程序启动了多少条线程,创建了多少个变量,它们都处于该Java虚拟机进程里,共享该JVM进程的内存区.当系统出现以下几种情况时,JVM进程将被终止: 程序运行到最后正常结束: 程序运行到使用System.exit()或Runtime.getRuntime.exit()代码结束程序: 程序运行过程中遇到未捕获的异常或错误而结束: 程序所在的平台强制结束了JVM进程. 类的加载 当程序主动使用某个类时,

java类的加载、链接、初始化

JVM和类的关系 当我们调用JAVA命令运行某个java程序时,该命令将会启动一条java虚拟机进程,不管该java程序有多么复杂,该程序启动了多少个线程,它们都处于该java虚拟机进程里.正如前面介绍的,同一个JVM的所有线程.所有变量都处于同一个进程里,它们都使用该JVM进程的内存区. 当系统出现以下几种情况时,JVM进程将被终止: l  程序运行到最后正常结束. l  程序运行到使用System.exit()或Runtime.getRuntime().exit()代码结束程序 l  程序执