zoukankan      html  css  js  c++  java
  • JVM学习4--类装载、生命周期

    一、类的生命周期

      首先要明确,类的生命周期是有五个阶段,而我们平时所说的类加载或者类装载,是指前三个阶段,即:加载、连接、初始化。

    二、类装载概述

      在这块,大体上讲一下类装载的概念,然后有几个demo,产生的现象我们在细说加载、连接、初始化三个阶段时解释。

      类装载的条件: Class只有在被使用的时候才会被装载,Java虚拟机不会无条件地装载Class。Java虚拟机规定,一个类或接口被使用之前一定要进行初始化。这里的“使用”,指的是直接使用,也称直接引用,直接引用有以下几种情况:

    •  当创建一个类的实例时,比如new,或者用过反射、克隆、类序列化。
    •  当调用类的静态方法时,即当使用了字节码invokestatic指令。
    •  当使用类或接口的静态字段时(final常量除外),比如,使用getstatic或者putstatic指令。
    •  当使用java.lang.reflect包中的方法反射类的方法时。
    •  当初始化子类时,要求先初始化父类。
    •  作为启动虚拟机,含有main()方法的那个类。

      例①  是一个直接引用和间接引用的例子:

    class Parent{
        static {
            System.out.println("Parent init");
        }
        public static int v = 100;
    }
    
    class Child extends Parent{
        static {
            System.out.println("Child init");
        }
    }
    
    public class IndirectReferencing {
        public static void main(String[] args) {
            // 直接引用(父类会先初始化,子类也会初始化)
            // 如果两步不分开执行,父类也只会初始化一次,这是ClassLoader做的保障,下面会提到
            Child child = new Child();
    
            //间接引用(子类不会初始化)
            System.out.println(Child.v);
        }
    }

      例②  引用常量也不会引起初始化:

    class FinalField{
        public static final String cons = "CONST";
        static {
            System.out.println("FinalField init");
        }
    }
    
    public class UseConstant {
        public static void main(String[] args) {
            // FinalField类不会初始化
            System.out.println(FinalField.cons);
        }
    }

      我们可以在运行程序时加上-XX:+TraceClassLoading参数观察类的加载情况:

      例① 间接引用的情况:可以看到子类没有被初始化,但是子类其实也被加载进来了,这就是类生命周期第三阶段初始化的特点:直接引用初始化,间接引用不初始化。

        [Loaded JvmTest.ClassLoaderT.Parent from file:/D:/IntelliJ-idea/experiment/out/production/experiment/]
        [Loaded JvmTest.ClassLoaderT.Child from file:/D:/IntelliJ-idea/experiment/out/production/experiment/]
        Parent init
        100
        [Loaded java.lang.Shutdown from C:Program FilesJavajdk1.8.0_144jrelib t.jar]

      例②  可以发现这次FinalField都没有加载,那么我们可以看出一个事情,类就算不加载,常量依然可以使用,这是为什么呢?下面说连接阶段细说。

        [Loaded java.lang.Void from C:Program FilesJavajdk1.8.0_144jrelib t.jar]
        CONST
        [Loaded java.lang.Shutdown from C:Program FilesJavajdk1.8.0_144jrelib t.jar]

      也可以用字节码观察一下程序,javap -c xxx.class javap -verbose xxx.class :

      例② 

    public class JvmTest.ClassLoaderT.UseConstant {
      public JvmTest.ClassLoaderT.UseConstant();
        Code:
           0: aload_0
           1: invokespecial #1                  // Method java/lang/Object."<init>":()V
           4: return
    
      public static void main(java.lang.String[]);
        Code:
           0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
           3: ldc           #4                  // String CONST
           5: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
           8: return
    }

      观察main函数,使用了ldc,在字节码偏移3的位置,将常量池第4项入栈,在这里第四项是:

       #4 = String             #25            // CONST

      可以看到,在编译后的UseConstant类中,并没有引用FinalField类,而是直接把常量存到了常量池中,所以FinalField自然不会加载。

    三、类装载之加载阶段

      加载类处于类装载的第一阶段。在加载类时,JVM完成了以下工作:

    1.  通过类的全名,获得类的二进制数据流。
    2.  解析类的二进制流为方法区内的数据结构。
    3.  创建java.lang.Class类的实例,表示该类型。

      在获得到类的二进制信息后,java虚拟机就会处理这些数据,最终转换为一个java.lang.Class的实例,java.lang.Class实例是访问类型元数据的接口,也是实现反射的关键数据。通过Class类提供的接口,可以访问一个类型的方法、字段等信息。例子:Class.forname("java.lang.String")

    四、 类装载之连接阶段

      1. 验证:目的是保证加载的字节码是合法、合理并符合规范的。

       

      2. 准备:虚拟机为类分配相应的内存空间,并设置初始值。

      final常量,直接就被赋值了。

      static普通静态变量,准备阶段只赋默认的初始值,在clinit中才会被设置为制定个值。例如:

      public static int v = 1; 准备阶段v被设置为0,clinit(初始化阶段)中设置为1。

      回答上面例2的疑问,为什么FinalField没有被加载呢?

        因为在连接阶段,常量可以直接被赋值,因此,FinalField类根本没被引用(直接引用或间接引用),自然不会被加载。

      3. 解析类: 将类、接口、字段、方法的符号引用转为直接引用

        在程序实际运行时,只有符号引用是不够的,例如当println()方法被调用时,系统需要明确知道该方法中的位置。以方法为例,Java虚拟机为每个类都准备了一张方法表,将其所有的方法都列在表中,当需要调用一个方法时,只要知道这个方法在方法表中的偏移量就可以直接调用该方法。通过解析操作,符号引用就可以转变为目标方法在类中方法表中的位置,从而使得方法被成功调用。可以说,如果直接引用存在,那么可以肯定系统中存在该类、方法、或者字段,但只存在符号引用,不能确定系统中一定存在该对象。

      

    四、类装载之初始化阶段

      初始化是类装载的最后一个阶段。此时,类才会开始执行Java字节码。初始化阶段的重要工作是执行类的初始化方法<clinit>。方法<clinit>是由编译器自动生成的,它是由静态成员变量的赋值语句以及static语句块合并产生的。

      

  • 相关阅读:
    字符数组,字符指针,字符串常量以及其sizeof的一些总结
    Linux——makefile
    动态定义多维数组
    二叉树的前序、中序、后序遍历(非递归)
    构造函数、析构函数抛出异常的问题
    倒排索引
    宏定义
    sizeof && strlen
    搜索引擎技术原理
    最小生成树
  • 原文地址:https://www.cnblogs.com/NoYone/p/8989916.html
Copyright © 2011-2022 走看看