Java中的初始化和清理及类的加载

本文深入探讨了Java中的类加载过程,包括静态和非静态内容的加载、静态变量的初始化以及静态代码块的执行。同时,详细阐述了对象创建的步骤,包括内存分配、默认初始化、构造函数的执行等。此外,还涵盖了构造器的作用、this关键字的使用、静态方法的特点以及垃圾回收机制。最后,通过实例分析展示了类加载和对象创建的具体执行顺序,强调了继承关系下的加载顺序和构造函数调用规则。
摘要由CSDN通过智能技术生成

类加载过程:

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

2, 在.class加载到方法区时,会分为两部分加载:先加载非静态内容,再加载静态内容

3, 加载非静态内容:把.class中的所有非静态内容加载到方法区下的非静态区域内

4, 加载静态内容:

4.1、把.class中的所有静态内容加载到方法区下的静态区域内

4.2、静态内容加载完成之后,对所有的静态变量进行默认初始化

4.3、所有的静态变量默认初始化完成之后,再进行显式初始化

4.4、当静态区域下的所有静态变量显式初始化完后,执行静态代码块

5,当静态区域下的静态代码块,执行完之后,整个类的加载就完成了。

 

对象创建过程:

1, 在堆内存中开辟一块空间

2, 给开辟空间分配一个地址

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

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

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

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

6.1、隐式三步:

1,执行super语句    (继承时讲解)

2,对开辟空间下的所有非静态成员变量进行显式初始化

3,执行构造代码块

   6.2、在隐式三步执行完之后,执行构造函数中书写的代码

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

 

构造器:

  1. java在创建对象时,用构造器保证对象被初始化。
  2. 使用new关键字,将会为对象分配存储空间,并调用相对应的构造器。
  3. 构造器名称与类名相同。
  4. 不接受任何参数的构造器称为默认构造器,也称为无参构造器;
  5. 构造器也是方法无返回值,和void空返回值不同;
  6. 构造器方法和普通方法的区别:
  7. 方法名和类名称相同,首字母大写
  8. 无返回值类型

方法重载:

  1. 方法签名相同,参数类型不同;
  2. 方法签名:返回值类型+方法名

this关键字:

  1. this表示当前对象,this关键字只能在方法内部使用,表示对“调用方法的哪个对象”的引用;
  2. 在构造器中调用构造器,可以使用this关键字;
  3. 例如调用无参构造器,可以在有参构造器中加入this();

static的含义:

static方法就是没有this的方法。在static方法内部不能调用非静态方法

清理:终结处理和垃圾回收

finalize

  • Java 技术允许使用 finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的。它是在 Object 类中定义的,因此所有的类都继承了它。

工作原理:

  • 工作原理应该是这样的:一旦垃圾收集器准备好释放对象占用的存储空间,它首先调用finalize(),而且只有在下一次垃圾收集过程中,才会真正回收对象的内存。所以如果使用finalize(),就可以在垃圾收集期间进行一些重要的清除或清扫工作(如关闭流等操作)。
  • 使用垃圾回收器的原因是为了回收程序不再使用的内存。

终结条件:

  • System.gc()用于强制进行终结动作

垃圾回收机制原理:

不同模式有不同机制;

  • 引用记数是一种简单但是速度很慢的垃圾回收技术;
  • 每个对象都含有一个引用记数器,当有引用连接上对象时,引用记数加1。当引用离开作用域或者为null时,引用记数减一。当引用计数器为0时,垃圾回收器会释放资源。

Java初始化及类的加载

加载过程:

  • 先加载非静态区域(未实例化),再加载静态区域默认初始化、显示初始化,然后执行静态代码块。父类先加载。

对象创建过程:

  • 对所有非静态成员变量进行默认初始化,调用构造函数,而构造函数分三步走,
  • 1、执行super语句,
  • 2、对所有非静态成员变量进行显式初始化,
  • 3、执行构造代码块。

所以在执行构造函数的代码块之前所有成员变量都已经经过显示初始化。在整个构造函数执行完并弹栈后,把空间分配的地址赋值给一个引用对象。

一、无继承关系

  • 1、初始化静态成员变量。
  • 2、执行静态代码块。
  • 3、初始化非静态成员变量。
  • 4 调用构造函数。
  • 5、把空间分配的地址赋值给一个引用对象。

二、存在继承关系

  • 1、初始化父类静态成员变量。
  • 2、执行父类静态代码块。
  • 3、初始化子类静态成员变量。
  • 4、执行子类静态代码块。
  • 5、初始化父类非静态成员变量。
  • 6、执行父类非静态代码块。
  • 7、执行父类构造函数。
  • 8、初始化子类非静态成员变量。
  • 9、执行子类非静态代码块、执行子类构造函数。
  • 10、把空间分配的地址赋值给一个引用对象。
  • 创建一个有继承关系的子类对象流程的简洁总结:先加载父类,再加载子类,实例化父类,最后实例化子类

1.栈、堆、方法区分别存放什么

栈: 用来运行函数。可以存储局部信息
堆:用来存储new出来的实体 (特点:每一个实体所开辟的空间都有一个地址;每一个实体中存储的数据都有一个默认初始值)
方法区:存放.class和static (方法区其实是由N多个小的区域构成。有存放非静态内容的非静态区域,还有存放静态内容的静态区域,还有存放常量的常量池等)

2.类加载过程

  1. JVM会先去方法区中找有没有相应类的.class存在。如果有,就直接使用;如果没有,则把相关类的.class加载到方法区
  2. 在.class加载到方法区时,会分为两部分加载:先加载非静态内容,再加载静态内容
    1). 把.class中的所有非静态内容加载到方法区下的非静态区域内
    2). 加载静态内容:
    2.2.1. 把.class中的所有静态内容加载到方法区下的静态区域内
    2.2.2. 对所有的静态变量进行默认初始化
    2.2.3. 对静态变量进行显式初始化
    2.2.4. 执行静态代码块

3.对象创建过程

  1. 在堆内存中开辟一块空间
  2. 给开辟空间分配一个地址
  3. 把对象的所有非静态成员加载到所开辟的空间下
  4. 对所有非静态成员变量进行默认初始化
  5. 调用构造函数,分为两部分:
    先执行构造函数中的隐式三步,再执行构造函数中书写的代码。
    1)隐式三步:
    1,执行super语句
    2,对开辟空间下的所有非静态成员变量进行显式初始化
    3,执行构造代码块
    2)执行构造函数中书写的代码
  6. 把空间分配的地址赋值给一个引用对象

举例

class Person {  
    int age;  

    Person(int age) {  
        this.age = age;  
    }  
}  

class Student extends Person {  
    // 2、对开辟空间下的所有非静态成员变量进行显式初始化  
    String school = "abc";  

    Student(String school, int age) {  
        // 1、执行super语句  
        super(age);  
        // 4、在隐式三步执行完之后,执行构造函数中书写的代码  
        this.school = school;  
    }  

    {  
        // 3、执行构造代码块  
        System.out.println("Student code block");  
    }  
}  

4.Person p= new Person();在内存中做了什么?

A:将Person.class文件加载到内存中。
B:在堆内存中创建一个对象Person。
C:把Person中的属性进行默认初始化。
D:把Person中的属性进行显式初始化。
E:调用构造代码块
F:调用构造函数进行初始化。
G:在栈内存中声明Person类型的变量p。
H:把堆内存的地址(引用)赋给了栈内存中p。

5.一道奇葩的题目不知道你会不会哦?

class SingleTon {
    private static SingleTon singleTon = new SingleTon();
    public static int count1;
    public static int count2 = 0;

    private SingleTon() {
        count1++;
        count2++;
    }

    public static SingleTon getInstance() {
        return singleTon;
    }
}

public class Test {
    public static void main(String[] args) {
        SingleTon singleTon = SingleTon.getInstance();
        System.out.println("count1=" + singleTon.count1);
        System.out.println("count2=" + singleTon.count2);
    }
}

打印:
count1=1

count2=0

分析:
1.SingleTon singleTon = SingleTon.getInstance();调用类SingleTon的静态方法,触发了类的初始化
2. 类加载时在准备过程中为类的静态变量分配内存并初始化默认值 singleton=null count1=0,count2=0
3. 类初始化后,为类的静态变量赋值和执行静态代码快。singleton赋值为new SingleTon()调用类的构造方法
4. 调用类的构造方法后count1和count2都要自增加1,因此count=1 count2=1
5. 执行打印语句:继续为count1与count2赋值,此时count1没有赋值操作,因此count1为1,但是count2执行赋值操作就变为0

 

实例问题

实例代码

Parent类

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

Child类

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

Test测试类

 
  1. 1 package mytest.javaBase;
  2. 2
  3. 3 public class Test {
  4. 4 public static void main(String[] args) {
  5. 5 Child demo = new Child();
  6. 6 demo.function();
  7. 7 System.out.println("…………………………………………………………………………………………………………………………");
  8. 8 Child child = new Child();
  9. 9 child.function();
  10. 10 }
  11. 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、开始加载对应的构造方法,执行隐式三步

 
  1. ①有个隐式的super();
  2.  
  3. ②显示初始化(给所有的非静态的成员变量)
  4.  
  5. ③执行构造代码块
  6.  
  7. 之后才开始执行本类的构造方法中的代码

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、在构造函数入栈执行时,分为两部分:先执行构造函数中的隐式三步,再执行构造函数中书写的代码。

 
  1. 隐式三步:
  2. ①执行super()语句
  3.  
  4. ②显示初始化(对开辟空间下的所有非静态成员变量进行)

    ③执行构造代码块

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(),即其父类的默认构造方法,这也是为什么一个父类通常要提供默认构造方法的原因;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值