• JVM


    类的生命周期:

     类加载器(class loader)是用来加载Java类到Java虚拟机中。

      一般来说,Java虚拟机使用Java类的方式如下:

      Java源程序(.java 文件)在经过Java编译器编译之后就被转换成Java字节代码(.class 文件)。类加载器负责读取Java字节代码,并转换成java.lang.Class类的一个实例。

      每个这样的实例用来表示一个Java类。通过此实例的newInstance()方法就可以创建出该类的一个对象。实际的情况可能更加复杂,比如Java字节代码可能是通过工具动态生成的,也可能是通过网络下载的。

      基本上所有的类加载器都是java.lang.ClassLoader类的一个实例。

    其实我们研究类加载器主要研究的就是类的生命周期。

    Java虚拟机中的几个比较重要的内存区域:

      堆区:   用于存放类的对象实例。

      栈区:   也叫java虚拟机栈,是由一个一个的栈帧组成的后进先出的栈式结构,栈桢中存放方法运行时产生的局部变量、方法出口等信息。当调用一个方法时,虚拟机栈中就会创建一个栈帧存放这些数据,当方法调用完成时,栈帧消失,如果方法中调用了其他方法,则继续在栈顶创建新的栈桢。

      常量池: 常量池是方法区的一部分,主要用来存放常量和类中的符号引用等信息。

      方法区: java虚拟机中有一块专门用来存放已加载的类信息、常量、静态变量以及方法代码的内存区域。

    类的生命周期

       当我们编写一个java的源文件后,经过编译会生成一个后缀名为class的文件,这种文件叫做字节码文件,只有这种字节码文件才能够在java虚拟机中运行。

      java类的生命周期就是指一个class文件从加载到卸载的全过程。

      一个java类的完整的生命周期会经历加载、连接、初始化、使用、和卸载五个阶段,当然也有在加载或者连接之后没有被初始化就直接被使用的情况,这里我们主要来研究类加载器所执行的部分,也就是加载,链接和初始化。如图所示:

         

    下面我先简单看一下类加载器所执行的三部分的简单介绍

      1加载:查找并加载类的二进制数据 

      2连接 

            –验证:确保被加载的类的正确性 

            –准备:为类的静态变量分配内存,并将其初始化为默认值 

            –解析:把类中的符号引用转换为直接引用 

      3初始化:为类的静态变量赋予正确的初始值 

     

      从上边我们可以看出类的静态变量赋了两回值。

      这是为什么呢?原因是,在连接过程中时为静态变量赋值为默认值,也就是说,只要是你定义了静态变量,不管你开始给没给它设置,系统都为他初始化一个默认值。到了初始化过程,系统就检查是否用户定义静态变量时有没有给设置初始化值,如果有就把静态变量设置为用户自己设置的初始化值,如果没有还是让静态变量为初始化值

      类的加载、连接和初始化

        

    类的加载:

        类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。

        这里的class对象其实就像一面镜子一样,外面是类的源程序,里面是class对象,它实时的反应了类的数据结构和信息。

     

    加载.class文件的方式

      1、从本地系统中直接加载 

      2、通过网络下载.class文件 

      3、从zipjar等归档文件中加载.class文件 

      4、从专有数据库中提取.class文件 

      5、将Java源文件动态编译为.class文件 

     

    类的加载过程

     

    结论:

      1、类的加载的最终产品是位于堆区中的Class对象 

      2Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口 

     Java虚拟机给我们提供了两种类加载器:

      1Java虚拟机自带的加载器 

           a. 根类加载器(使用C++编写,程序员无法在Java代码中获得该类) 

           b. 扩展加载器,使用Java代码实现 

           c. 系统加载器(应用加载器),使用Java代码实现 

      2、用户自定义的类加载器 

            java.lang.ClassLoader的子类 

              用户可以定制类的加载方式 

     

    我们看一下APIClassLoader的介绍:

      类加载器是负责加载类的对象。

      ClassLoader 类是一个抽象类。如果给定类的二进制名称,那么类加载器会试图查找或生成构成类定义的数据。一般策略是将名称转换为某个文件名,然后从文件系统读取该名称的类文件。每个class对象都包含一个对定义它的 ClassLoader 的引用。

     

    我们再来看一下Class类的一个方法getClassLoader

      public ClassLoader getClassLoader()

      返回该类的类加载器。有些实现可能使用null来表示根类加载器。如果该类由根类加载器加载,则此方法在这类实现中将返回null 

     

      类加载器并不需要等到某个类被首次主动使用时再加载它。

      JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了.class文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError错误)

      如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误。大家在做web开发的时候有可能会出现这种问题,比如我们在做测试的时候是用的jdk1.6,而我们在部署的时候我们用的是jdk1.5.这时候就很可能汇报LinkageError错误,版本不兼容。

      

    类的连接:

      类被加载后,就进入连接阶段。

      连接就是将已经读入到内存的类的二进制数据合并到虚拟机的运行时环境中去。 

    验证:

      当一个类被加载之后,必须要验证一下这个类是否合法,比如这个类是不是符合字节码的格式、变量与方法是不是有重复、数据类型是不是有效、继承与实现是否合乎标准等等。总之,这个阶段的目的就是保证加载的类是能够被jvm所运行。

      很多人都感觉,既然这个类都通过编译加载到内存里了,那肯定就是合法的了,为什么还要验证呢,这是因为这里的验证时为了避免有人恶意编写class文件,也就是说并不是通过编译得到的class文件。所以这里验证其实是检查的class文件的内部结构是否符合字节码的要求。

    准备:

      准备阶段的工作就是为类的静态变量分配内存并设为jvm默认的初值,对于非静态的变量,则不会为它们分配内存。

      有一点需要注意,这时候,静态变量的初值为jvm默认的初值,而不是我们在程序中设定的初值。

      jvm默认的初值是这样的:

      基本类型(intlongshortcharbytebooleanfloatdouble)的默认值为0。引用类型的默认值为null

      常量的默认值为我们程序中设定的值,比如我们在程序中定义final static int a = 100,则准备阶段中a的初值就是100

    解析:这一阶段的任务就是把常量池中的符号引用转换为直接引用。

        

    那么什么是符号引用,什么又是直接引用呢?

      我们来举个例子:我们要找一个人,我们现有的信息是这个人的身份证号是1234567890。只有这个信息我们显然找不到这个人,但是通过公安局的身份系统,我们输入1234567890这个号之后,就会得到它的全部信息:比如山东省滨州市滨城区18号张三,通过这个信息我们就能找到这个人了。这里,123456790就好比是一个符号引用,而山东省滨州市滨城区18号张三就是直接引用。

      在内存中也是一样,比如我们要在内存中找一个类里面的一个叫做show的方法,显然是找不到。但是在解析阶段,jvm就会把show这个名字转换为指向方法区的的一块内存地址,比如c17164,通过c17164就可以找到show这个方法具体分配在内存的哪一个区域了。这里show就是符号引用,而c17164就是直接引用。在解析阶段,jvm会将所有的类或接口名、字段名、方法名转换为具体的内存地址。 

      

    类的初始化:

      在类的生命周期执行完加载和连接之后就开始了类的初始化。

      在类的初始化阶段,java虚拟机执行类的初始化语句,为类的静态变量赋值。

    在程序中,类的初始化有两种途径:

      (1) 在变量的声明处赋值

      (2) 在静态代码块处赋值

      比如下面的代码,a就是第一种初始化,b就是第二种初始化

        public class Test{

          public static int a = 0;

          public static int b;

          static {

            b = 2;

          }

        }

     静态变量的声明和静态代码块的初始化都可以看做静态变量的初始化,类的静态变量的初始化是有顺序的。

     

    不知道大家的答案是不是这个,反正我的是。下面我们来看一下正确答案:

      Counter1=1

      Counter2=0

     

     不知道你做对没有,反正我刚开始做错了。

     

    好,现在我来解释一下为什么会是这个答案。在给出解释之前,我们先来看一个概念:

    Java程序对类的使用方式可分为两种

      (1) 主动使用

      (2) 被动使用

     所有的Java虚拟机实现必须在每个类或接口被Java程序首次主动使用时才初始化他们.

     

    主动使用:

      ① 创建类的实例 

      ② 访问某个类或接口的静态变量,或者对该静态变量赋值 

      ③ 调用类的静态方法 

      ④ 反射(Class.forName(“com.alibaba.Test”)) 

      ⑤ 初始化一个类的子类 

      ⑥ Java虚拟机启动时被标明为启动类的类(Java Test) 

     

      OK,我们开始解释一下上面的答案,程序开始运行,首先执行main方法,执行main方法第一条语句,调用Singleton类的静态方法,这里调用Singleton类的静态方法就是主动使用Singleton类。所以开始加载Singleton类。在加载Singleton类的过程中,首先对静态变量赋值为默认值,

      Singleton=null

      counter1 = 0

      Counter2 = 0

      给他们赋值完默认值值之后,要进行的就是对静态变量初始化,对声明时已经赋值的变量进行初始化。我们上面提到过,初始化是从类文件从上到下赋值的。所以首先给Singleton赋值,给它赋值,就要执行它的构造方法,然后执行counter1++;counter2++;所以这里的counter1 = 1;counter2 = 1;执行完这个初始化之后,然后执行counter2的初始化,我们声明的时候给他初始化为了,所以counter2 的值又变为了0.

    初始化完之后执行输出。所以这里是:

      counter1 = 1

      counter2 = 0

     

    类初始化步骤:

      (1) 假如一个类还没有被加载或者连接,那就先加载和连接这个类

      (2) 假如类存在直接的父类,并且这个父类还没有被初始化,那就先初始化直接的父类

      (3) 假如类中存在初始化语句,那就直接按顺序执行这些初始化语句

       在上边我们我们说了java虚拟机实现必须在每个类或接口被Java程序“首次主动使用”时才初始化他们,上面也举出了六种主动使用的说明。除了上述六种情形,其他使用Java类的方式都被看作是被动使用,不会导致类的初始化。程序中对子类的“主动使用”会导致父类被初始化;但对父类的“主动”使用并不会导致子类初始化(不可能说生成一个Object类的对象就导致系统中所有的子类都会被初始化) 

     

    注:调用ClassLoader类的loadClass方法加载一个类,并不是对类的主动使用,不会导致类的初始化。

      当java虚拟机初始化一个类时,要求它的所有的父类都已经被初始化,但这条规则并不适用于接口。

      在初始化一个类时,并不会先初始化它所实现的接口.

      在初始化一个接口时,并不会先初始化它的父接口.

       因此,一个父接口并不会因为它的子接口或者实现类的初始化而初始化。只有当程序首次使用特定接口的静态变量时,才会导致该接口的初始化。只有当程序访问的静态变量或静态方法确实在当前类或当前接口中定义时,才可以认为是对类或接口的主动使用 。如果是调用的子类的父类属性,那么子类不会被初始化。

    类的生命周期:

    类的生命周期从类被加载,连接和初始化开始!

    到类的卸载结束!

     01:类的生命周期中,类的二进制数据位于方法区

     02:在堆中会有一个描述这个类的Class对象;

    加载:

      01:将class字节码文件内容加载到内存中,并将这些静态数据转化成方法区中运行时数据结构!在堆中生成class对象!

    02:这个class对象就是方法取得数据访问入口!

    连接:

      01:类的验证

           合法???

        001:类文件的结构检查

        002:语义检查  比如说final修饰的变量在编译期间发现再次赋值

        003:字节码验证 ,确保字节码能被JVM识别

    02:类的准备:

        为类中的static修饰的内容开辟空间,并且赋予初始值

        Static int num=100

    这时num=0

    03:类的解析:

    把类中的符号引用转换成直接引用

        符号引用:就是用字符串的形式来表示某个变量或者是我们能看懂的

        Sting a=”a”;

        Student  stu =new Student();

    a, stu都是符号引用

        直接引用:是根据引用翻译出来的地址

    图示:

    初始化:

      加载不是初始化

    初始化指的是实例化!创建出类的实例!

    初始化的时机:

      01:类的主动引用

          001new一个类的对象

      002:通过反射的newInstance()

    003:在初始化子类的时候必须初始化父类

          02:类的被动引用

              001:通过类名访问静态的内容

              002:调用类的静态常量也不会初始化类

              003:用类作为对象初始化数组,不会初始化数组

              004:子类调用父类的静态变量不会加载子类的静态代码块(不会执行类的执行阶段)  

    案例整合:

    01:创建实体类A B C

      package com.dzq;

    public class A {
        public A(){
            System.out.println("A的初始化");
        }

    static {
            System.out.println("A的静态代码块");
        }
    }

    02:创建B类继承A

    package com.dzq;
    public class extends A
    {
        public static int NUM=1;//静态变量
        public static  final int NUMBER=1;//静态常量
      public B(){
          System.out.println("B的初始化");
      }
      static {
          System.out.println("B的静态代码块");
      }
    }

    03:创建C类继承B

    package com.dzq;
    public class extends B {
        public C(){
            System.out.println("C的初始化");
        }
        static {
            System.out.println("C的静态代码块");
        }
    }

    04:写测试类

    package com.dzq;

    public class TestDemo {
        public static void main(String[] args) throws ClassNotFoundException {
          // new B();//实例化B类的对象

    运行结果:

    A的静态代码块

    B的静态代码块

    A的初始化

    B的初始化


            //System.out.println(B.NUM);//类名.静态变量

    运行结果:

    A的静态代码块

    B的静态代码块

    1


            //System.out.println(B.NUMBER);//类名.静态变量

    运行结果:

    1
           // Class.forName("com.dzq.B");

    运行结果:

    A的静态代码块

    B的静态代码块
            //B[]bs=new B[2];//数组

    运行结果:

    什么也不输出
           //System.out.println(C.NUM);

    运行结果:

    A的静态代码块

    B的静态代码块

    1


        }
    }

     

  • 相关阅读:
    Javaweb中的监听器
    Maven
    Ajax
    jQuery
    Spring入门
    spring boot实战——微信点餐系统02:接口信息,分页工具,gson, 注解信息,表单验证,字段返回(时间处理,null处理)
    Nginx
    Spring Data JPA
    spring boot实战——微信点餐系统01:开始代码
    spring boot实战——微信点餐系统00:项目设计,环境搭建
  • 原文地址:https://www.cnblogs.com/wsnb8/p/9359582.html
Copyright © 2020-2023  润新知