Java 类中各成分加载顺序 和 内存中的存放位置

参加一个笔试,有一个关于类的静态代码块、构造代码块、构造函数的执行顺序的问题。不太清楚,网上百度了一下。在这里记录一下。

一、什么时候会加载类?
使用到类中的内容时加载:有三种情况
1.创建对象:new StaticCode();
2.使用类中的静态成员:StaticCode.num=9;  StaticCode.show();
3.在命令行中运行:java StaticCodeDemo
二、类所有内容加载顺序和内存中的存放位置:
利用语句进行分析。
1.Person p=new Person("zhangsan",20);
该句话所做的事情:
1.在栈内存中,开辟main函数的空间,建立main函数的变量 p。
2.加载类文件:因为new要用到Person.class,所以要先从硬盘中找到Person.class类文件,并加载到内存中。
加载类文件时,除了非静态成员变量(对象的特有属性)不会被加载,其它的都会被加载。
记住:加载,是将类文件中的一行行内容存放到了内存当中,并不会执行任何语句。---->加载时期,即使有输出语句也不会执行。
静态成员变量(类变量)  ----->方法区的静态部分
静态方法              ----->方法区的静态部分
非静态方法(包括构造函数)  ----->方法区的非静态部分
静态代码块 ----->方法区的静态部分
构造代码块 ----->方法区的静态部分
注意:在Person.class文件加载时,静态方法和非静态方法都会加载到方法区中,只不过要调用到非静态方法时需要先实例化一个对象
,对象才能调用非静态方法。如果让类中所有的非静态方法都随着对象的实例化而建立一次,那么会大量消耗内存资源,
所以才会让所有对象共享这些非静态方法,然后用this关键字指向调用非静态方法的对象。
3.执行类中的静态代码块:如果有的话,对Person.class类进行初始化。
4.开辟空间:在堆内存中开辟空间,分配内存地址。
5.默认初始化:在堆内存中建立 对象的特有属性,并进行默认初始化。
6.显示初始化:对属性进行显示初始化。
7.构造代码块:执行类中的构造代码块,对对象进行构造代码块初始化。
8.构造函数初始化:对对象进行对应的构造函数初始化。
9.将内存地址赋值给栈内存中的变量p。
2.p.setName("lisi");
1.在栈内存中开辟setName方法的空间,里面有:对象的引用this,临时变量name
2.将p的值赋值给this,this就指向了堆中调用该方法的对象。
3.将"lisi" 赋值给临时变量name。
4.将临时变量的值赋值给this的name。
3.Person.showCountry();
1.在栈内存中,开辟showCountry()方法的空间,里面有:类名的引用Person。
2.Person指向方法区中Person类的静态方法区的地址。
3.调用静态方法区中的country,并输出。
  注意:要想使用类中的成员,必须调用。通过什么调用?有:类名、this、super
  三、静态代码块、构造代码块和构造函数的区别
静态代码块:用于给类初始化,类加载时就会被加载执行,只加载一次。
构造代码块:用于给对象初始化的。只要建立对象该部分就会被执行,且优先于构造函数。
构造函数:  给对应对象初始化的,建立对象时,选择相应的构造函数初始化对象。
 创建对象时,三者被加载执行顺序:静态代码块--->构造代码块--->构造函数
 //利用代码进行测试 例题:06--06:StaticCodeDemo.java

[java] view plain copy
 
  1. class Person  
  2. {  
  3. private String name;  
  4. private int age=0;  
  5. private static String country="cn";  
  6. Person(String name,int age)  
  7. {  
  8. this.name=name;  
  9. this.age=age;   
  10. }  
  11. static  
  12. {  
  13. System.out.println("静态代码块被执行");  
  14. }  
  15. { System.out.println(name+"..."+age);}  
  16. public void setName(String name)  
  17. {  
  18. this.name=name;  
  19. }  
  20. public void speak()  
  21. {  
  22. System.out.println(this.name+"..."+this.age);  
  23. }  
  24. public static void showCountry()  
  25. {  
  26. System.out.println("country="+country);  
  27. }  
  28. }  
  29. class StaticDemo  
  30. {  
  31. static  
  32. {  
  33. System.out.println("StaticDemo 静态代码块1");  
  34. }  
  35. public static void main(String[] args)  
  36. {  
  37. Person p=new Person("zhangsan",100);  
  38. p.setName("lisi");  
  39. p.speak();  
  40. Person.showCountry();  
  41. }  
  42. static  
  43. {  
  44. System.out.println("StaticDemo 静态代码块2");  
  45. }   
  46. }  

输出结果:
 StaticDemo 静态代码块1
 StaticDemo 静态代码块2
 静态代码块被执行
 null...0    //构造代码块
 lisi...100  //speak()
 country=cn  //showCountry()

 写了这么长时间Java,总是不太清楚什么是类的加载,初始化,加载初始化又对应的是我们代码中的哪一段。还有就是静态代码块到底是在什么实际执行的。

         首先类加载包括  装载(也叫加载,区别于“类加载”,“加载”是“类加载”的一部分),连接(包括验证、准备、解析),初始化。

          1.加载

                   1)通过一个类的全部限定名来获取定义此类的二进制字节流。

                   2)将这个字节流所代表的静态储存结构转化为方法去的运行时数据结构

                   3)在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

                   注:该3点是取自深入理解Java虚拟机第二版,要我理解无非就是说要找到.class文件(一般情况下)然后读取到内存(方法区),Class本省也是一个对象,他等待对应的实例对象的调用。

         2.连接

                 1)验证,总之就是为加载的class进行验证,包括文件格式验证,元数据验证,字节码验证等等。。

                 2)准备,准备阶段是正式为类变量(static修饰的变量)分配内存并设置初始值。

                                该阶段只为静态变量分配内存(方法区),但是“通常情况”变量的值只是赋予初始值,真正的赋值是在类初始化的时候。即

                               public static int val = 1234;     val的值在准备阶段结束后的值为0;而不是1234。

                               例外情况:  public static final int val = 1234;  这时候就会把值设置成1234;

                 3)解析,没太理解清楚 不解释了。。。

          3.初始化 

                   当创建对象(new 或者反射等),调用类的静态方法,使用累的静态字段(已经初始化过值得除外,如final static修饰的常量),子类初始化时, 以及当虚拟机启动某个被 标明为启动类的类(即包含main方法的那个类)时,尽心初始化。  静态变量的复制,静态代码块的执行都在次阶段。

          4.自己的代码与jvm执行过程的理解

1)类1

package study.jiazai;
public class A {
    public static String in = "in in";
      static {  
        System.out.println(in);
    }
        public void print(){
                System.out.println("print...");
    }
}
2)类2
package study.jiazai;
public class Mainlcass {
    public static void main(String[] args) {
            System.out.println("..............");          
            A a;//声明定义对象此时执行了1.加载,2.准备阶段            
            System.out.println("---------------------");
            a = new A();//初始化阶段             
            System.out.println("====================");
            a.print();
    }
}
3)执行结果
..............
---------------------
in in//说明初始化之后执行的静态代码块
====================
print...
        5.困扰我好久的一个问题。
                      在我们写代码种应该经常写过类似如下的代码
                    写法1
                     for(int i = 0 ;i<100;i++){
                            A a = new A();
                            a.setString("123456");
                            a.setName("dashan");
                     }
                    写法2
                      A a=null;
                     for(int i = 0 ;i<100;i++){
                            a = new A();
                            a.setString("123456");
                            a.setName("dashan");
                    }

                    很久以前有人告诉我要用写法2,当时问了一句为什么记得这样可是省得每次分配内存,那时候也就这样记下了,可是后来根据对JVM的了解,

            了解到对象分配后又开始迷惑了,不知道这内存是不是正的省了,是到底怎么省得,今天搜了好多帖子也都没找到具体的答案,不过好在经过看书

             以及大家帖子的启发,自己思考了好久感觉终于想通了,拿出来分享下,如果有什么问题欢迎指正讨论。

                1.首先一开始我以为是省了对内存,但是想了想不过怎么都是要重新创建多次对象,没有任何我节约。

               2.今天终于吧注意力关注到栈内存了,对啊,原来是这样省去了每次循环分配栈内存的步骤。(注 不是省了栈内存,而是省去了没次都要重新分配占内存的步骤)

                          1)我们知道java的局部变量是占用的栈内存,同时随着局部代码块的结束销毁的。

                          2)在占中除了操作栈之外,还有另外重要一部分局部变量表,局部变量表中保存的是代码块中每个变量的实际值或者对象引用。(感觉今天主要是这点想通了)

                          3)基于第二点写法1中每次循环都要在档次循环的栈中(具体指局部变量表)创建内存空间;写法2中在代码块外面把局部变量表中的内存分配好对于循环代码块

                   它可以算作是一个全局变量只需要分配一次内存就可以在循环中使用了。

                3.综上所述,这样的写法应该是提升了分配内存的效率。。。

原文地址:https://www.cnblogs.com/lxl57610/p/5900895.html