java虚拟机类加载深入理解和阶段分析

01 类加载深入解析和阶段分解

  • java代码中,的加载,连接与初始化过程都是在程序运行期间完成的。

    • 类:指的是我们在程序中定义的一个class,interface,enum等。注意这里的类型并不是对象(new)。
      • 举例子:这里的类型是指Object类本身而不是Object new出来的实例对象。因为在实际的开发过程中一直对对象进行操作,所以类型这个概念可能没有太多的概念。因为在创建对象之前,jvm已经把改类已经加载完毕,我们程序才能直接进行创建对象。
  • 提供更大的灵活性,增加了更多的可行性。

  • 整体流程如下所示:

类加载示意图.jpg

01 加载

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

  • 加载.class文件的方式 – jvm规范中并没有明确规定.class文件的加载方式

    • 从本地系统中直接加载(也是大部分的加载方式)
    • 通过网络下载.class文件
    • 从zip,jar等归档文件中加载.class文件
    • 从专有数据库中提取,class文件
    • 将java源文件动态编译成.class文件 ----> 典型的场景:动态代理,jsp->servlet

02 连接

  • 验证:确保被加载的类的正确性
  • 准备:为类的静态变量分配内存,并将其初始化默认值
class Test{
   //准备阶段将静态变量a初始化默认值,int的默认值为0
   public static int a = 1;
}
  • 解析:把类的符号引用转换为直接引用。
    • 符号引用 – 间接的引用,用某个符号来表示引用关系。
    • 直接引用将对象指向类的内存地址(可以直接找到具体某个类)

03 初始化

  • 为类的静态变量赋予正确的初始值
class Test{
   //初始化阶段将静态变量a赋予正确的初始值,a =1 如何a 没有显示的赋值为1 那a不会再初始化阶段在进行赋值。
   public static int a = 1;
}

04 使用

  • java程序对类的使用方式分为两种:

    • 主动使用

      • 创建类的实例 —> 通过类new一个实例对象
      • 访问某个类/接口的静态变量,或者对改类的静态变量进行赋值
      • 调用类的静态方法
      • 反射调用
      • 初始化一个类的子类也是对该类进行的主动使用
      • java虚拟机启动时被表明为启动类(main/test)
      • jdk1.7开始提供了动态语言支持:java.lang.invoke.MethodHandle实例的解析结果为REF_getStatic,REF_putStatic,REF_invokeStatic句柄对应的类没有初始化,则初始化。
    • 被动使用

      • 除去主动使用,剩余的使用方式就是被动使用,被动使用是不会导致类的初始化。只是进行初始化,但是初始化之前的动作可能会进行,也可能不会进行。
  • 所有的java虚拟机实现必须在每个类/接口被java程序首次主动使用才会初始化他们。

    • 首次主动使用:第一次主动使用的方式才会导致类/接口的初始化,被动使用或者不使用就不会被初始化。而且初始化的动作只会执行一次。就是在首次使用的时候。

05 卸载

  • 就是将类从内存中移除,平常日常开发的过程中,我们很少会遇到。

java虚拟机与程序的生命周期

  • 在如下的几种情况下,java虚拟机讲结束生命周期
  1. 执行了System.exit()方法。
  2. 程序正常执行结束。
  3. 程序在执行的过程中遇到了异常或错误而导致的异常结束。
  4. 由于操作系统出现错误而导致java虚拟机进程终止。

Coding

  • 主动使用的案例
    public static void main(String[] args) {
        /**
         * 场景1: 
         * 通过子类调用父类的str静态变量,我们只需要关注str隶属于谁就是对谁的直接调用
         * 对父类的来说是直接使用 父类就会被加载
         * 输出结果为
         * ----  Parent1 ----
         * hello
         */
        System.out.println(Child1.str);

        /**
         * 场景2:
         * 直接调用子类的静态变量 对子类进行直接调用
         * 子类就会被初始化 同时父类也会被初始化 而且父类要先行与子类进行初始化
         * 输出结果为
         * ----  Parent1 ----
         * ---- Child1 --------
         * hello
         */
        System.out.println(Child1.str2);

    }
}

class Parent1{
    public static String str = "hello";
    static {
        //静态代码块在类的初始化过程会加载
        System.out.println(" ----  Parent1 ----");
    }
}

class Child1 extends Parent1{
    public static String str2 = "hello";
    static {
        System.out.println("---- Child1 --------");
    }
}
  • 常量的本质以及相关助记符的讲解
/**
 * 描述:  常量的本质含义 讲解01
 * @author karl
 * @create 2019-09-30 17:57
 */
public class MyTest01 {
    public static void main(String[] args) {
        /**
         * 这个毫无疑问 直接调用了Parent1的静态变量str Parent1进行了初始化
         * 控制台打印如下所示:
         * ----- Parent1 ---------
         * hello
         */
        System.out.println(Parent1.str);
        /**
         * 调用了str2 str2本质是个常量
         * 常量在编译阶段会存到调用这个常量所在类(即MyTest01这个类)的常量池中
         * 本质上,调用类并没有直接引用到定义常量的类,而是直接调用本类常量池中的str2 所以Parent1不会被初始化静态代码块也不会被执行
         * 执行效果如下所示:
         * hello
         */
        System.out.println(Parent1.str2);
    }
}


class Parent1 {
    public static String str = "hello";
    public static final String str2 = "hello";

    static {
        System.out.println("----- Parent1 ---------");
    }
}
通过字节码进行分析

Compiled from "MyTest01.java"
public class com.study.jvm.day02.MyTest01 {
  public com.study.jvm.day02.MyTest01();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  //上述是MyTest01的无参构造方法的编译后的字节码,我们不做分析
  //主要看下面的main方法的字节码

  public static void main(java.lang.String[]);
    Code:
       // getstatic 助记符 获取静态熟悉/静态方法的
       // 0  getstatic 获取System.out out方法是静态方法
       0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
       // 03 是获取Parent1.str 调用了Parent1类的静态变量str
       3: getstatic     #3                  // Field com/study/jvm/day02/Parent1.str:Ljava/lang/String;
       6: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       9: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
       // ldc 助记符  -> int、float或String型常量从常量池推送至栈顶 这里的str2 直接展示的就是hello 
       //并没有通过Parent1进行调用 
      12: ldc           #6                  // String hello
      14: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      17: return
}

我们通过字节码的分析,也验证了我们案例的结论。

/**
 * 描述:  常量的讲解2
 * @author karl
 * @create 2019-09-30 21:22
 */
public class MyTest01 {
    public static void main(String[] args) {
       /**
         * 当一个常量的值在编译期间无法确定,那么就不会调用类的常量池中
         * 这时在运行期间就会主动使用Parent,使用Parent就会被初始化,运行静态代码块的内容
         * 输出结果如下所示:
         *
         * --- Parent -----
         * 5070fd4e-ff69-4f2d-82cb-dfb18b04a886
         *
         */
        System.out.println(Parent.randomStr);
    }
}

class Parent{
    /**
     * 编译期间是不知道具体的值,只有在运行期间才会知道
     */
    public static final String randomStr = UUID.randomUUID().toString();

    static {
        System.out.println("--- Parent -----");
    }
}
同时这个过程也可以通过字节码进行分析,在这里就不做演示了。

  • 数组的理解
/**
 * 描述:  数组的讲解
 *
 * @author karl
 * @create 2019-09-30 21:35
 */
public class Mytest02 {

    public static void main(String[] args) {
       /**
         * 对于数组的实例来说 其类似是由JVM在运行期动态生成的,动态代理一样 通过parent02s.getClass().getSuperclass()得知其父类是Object
         * new 出来的是Parent02[]数组的实例而不是Parent02的实例,所以不会导致Parent02 的初始化
         * 引用类的数组创建并不会导致类的初始化 所以Parent02的静态代码块不会被执行
         * 输出: 无输出
         */
        Parent02[] parent02s = new Parent02[1];
        int[] ints = new int[1];
    }
}

class Parent02{
    static {
        System.out.println("--- Parent02 ---");
    }
}
  • 如果文章有帮助到你欢迎关注微信公众号《后端学长》 在这里插入图片描述
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值