深入理解JVM(1)

  1. 类加载
  • 在java代码中,类型的加载、连接、初始化过程都是在程序运行期间完成的
  • 提供了更大的灵活性,增加了 更多的可能性
    解释
    △注意此处的 “类型” 指的是类,也就是具体的 class, 而非对象。 举个例子就是:加载的 Objcet 这个类的二进制字节码文件,而不是 new Object 的对象,在这个阶段不存在对象的概念。
    △它加载的来源有这么几种:①磁盘上的字节码②动态生成的字节码③网络来源
    △还有一个重要的概念是 runtime,运行期间完成的,好多编译型语言都是在编译时完成的加载和连接,而java这样做,印证了第二句话。

    延伸:加载、连接、初始化JVM并非是严格按照这个顺序,JVM有相应的规范,但规范对某些地方做了详细规定,对有些地方仅做了一些描述。这样的话虚拟机厂商就可以自由的发挥了,只要最终的要求实现功能一致就可以,不用拘泥于其底层是怎么实现的,是不是和JDBC的很像?
    疑问:java本身是一门静态类型的语言,很多的特性又具有动态语言才能拥有的特质?
  1. 类加载器
  • 顾名思义,既然是“器”,那就是对应的类型加载的具体实现。而类加载做的事情不过是将字节码文件,无论何处来的,加载到内存当中,供JVM机进程使用。
  • JVM与程序的生命周期(遇到以下几种情况,java虚拟机将结束声明周期):
    ①执行了System.exit();
    ②程序正常执行结束
    ③程序在执行过程中遇到了异常或错误而异常终止
    ④由于操作系统出现错误导致JVM终止
  1. 类的加载、连接、初始化
    在这里插入图片描述
  • 加载:查找并加载类的二进制数据 (类加载器对应于这一过程)
  • 连接
    △ 验证:确保被加载类的正确性
    要理解的是java只不过是一门语言,是方便我们程序员来进行编程,提高效率而呈现出的一种语言形式,其核心在JVM机和运行的字节码文件,如果说其他的语言编译后也符合class文件的格式,那么它也能在JVM机上运行,和java没有区别。语言只不过是程序员和class文件之间的桥梁而已。你要是牛逼直接写二进制字节码文件也可以,但是不现实,效率太低,没有必要。因此在被类型加载之后所面临的问题是:验证字节码文件的正确性。因为很容易被篡改,无论是磁盘上的人为操作,或者网络上的字节码文件
    △ 准备:为类的静态变量分配内存,并将其初始化为默认值
    可以看到首要做的就是为静态变量赋值。不管有没有赋值,都得先初始化为默认值,之后在初始化阶段再赋值。可见程序并不聪明
    △ 解析:把类中的符号引用转换为直接引用
    画个图来解释直接引用和间接引用:
    在这里插入图片描述
  • 初始化:为类的静态变量赋予正确的初始值
  • 使用:调用对象、相关方法。 (运行中)
  • 卸载:class文件驻留在内存中使用完之后当然要卸载,一方面释放内存,另一方面方便后来人

  • java程序对类的使用方式可以分为两种
    △ 主动使用
    ①创建类实例
    ②访问某个类或者接口的静态变量(getstatic),或者对该静态变量赋值(putstatic),调用该类的静态方法(invokestatic)。
    ③反射(如Class.forName(“com.test.Test”))
    ④初始化一个类的子类
    很有意思啊这里,只有主动使用才会初始化,而方式只有这六种,而一个类的子类初始化那么对应的只有5种,加上最后一种不常见,所以有四种。看下面代码
    ⑤JVM启动时被标明为启动类的类(含有main方法的类)
    ⑥JDK1.7开始提供的动态语言支持java.lang.invoke.MethodHandle实例的解析结果REF_getStatic,REF_putStatic,REF_invokeStatic对应类没有初始化

    △ 被动使用
    除了主动使用之外就是被动使用,他两构成了一个全集

  • 所有的JVM实现都遵循了:在每个类或接口被JAVA程序“ 首次主动使用 ”时才初始化它们。
    除了以上六种情况之外,其他的都属于被动使用,都不会导致类的初始化。记住是初始化!加载、连接阶段可能有,也可能没有,唯一能够确定的是肯定没有初始化

  • 类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后内存中创建了一个java.lang.Class对象,用来封装在方法区内的数据结构。再次明确字节码文件加载的方式:
    ①从本地系统磁盘上加载
    ②通过网络下载
    ③从zip、jar等归档文件中加载.class文件
    ④从专有数据库中提取字节码文件
    ⑤将java源文件动态编译为字节码文件(动态代理、web开发)

  • 案例:

    package com.test;
    
    public class Test1 {
    	static {
    		System.out.println("main方法初始化");
    	}
    	public static void main(String[] args) {
    		System.out.println(Child.p);
    	}
    }
    
    class Parent{
    	public static String p = "我是爸爸";
    	
    	static {
    		System.out.println("父类初始化");
    	}
    }
    
    class Child extends Parent{
    //	public static String p = "我是儿子";
    	static {
    		System.out.println("子类初始化");
    	}
    }
    

    在这里插入图片描述
    去除注释之后:
    在这里插入图片描述
    注:父类必须先行初始化

    • 问:在第一个运行程序中子类既然没有初始化,那么它加载了吗?

    • 在这里插入图片描述
      在这里插入图片描述
      首先加载的类是Object类,在C:\Program Files\Java\jdk1.8.0_121\jre\lib\rt.jar下
      在这里插入图片描述
      可以看到三个类依次的加载顺序

      -XX:- 表示开启option选项
      -XX:+表示关闭option选项
      -XX:=表示将option选项的值设置为value

  • 案例2:
    在这里插入图片描述
    在这里插入图片描述
    常量在编译阶段会存到调用该常量的方法所在类的常量池中。本质上,调用类并没有直接引用到定义常量的类,因此并不会触发定义常量类的初始化
    注意:这里指的是将常量存放到了MyTest2常量池中,之后MyTest2和MyParent2就没有任何关系,甚至我们可以将MyParent2的class文件删除

    在这里插入图片描述
    反编译Test2.class:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    解释:这里的助记符其实都有对应的类实现,快捷键CTRL+SHIFT+T,查找对应的助记符(大写),会发现对应的类。如下图:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  • 运行期常量
    在这里插入图片描述
    仿照上一个案例,删掉编译后的Parent3.class文件,报出下列错误:
    在这里插入图片描述
    当一个静态常量的值并不能够在编译期确定下来,那么其值就不会放到调用类的常量池中。这时候程序运行时,会导致主动使用这个常量所在的类,显然会导致这个类被初始化
    在这里插入图片描述

    package com.test;
    public class Test4 {
    	public static void main(String[] args) {
    		//首次主动使用
    //		Parent4 parent4 = new Parent4();
    		Parent4 parent4[] = new Parent4[1];
    		System.out.println(parent4.getClass());
    		System.out.println(parent4.getClass().getSuperclass());
    		Parent4 parent5[][] = new Parent4[1][1];
    		System.out.println(parent5.getClass());
    		System.out.println(parent5.getClass().getSuperclass());
    	}
    }
    class Parent4{
    	static {
    		System.out.println("Parent4");
    	}
    }
    

    在这里插入图片描述
    对于数组实例来说,其类型是由JVM在运行期间动态生成的,表示为[Lcom.test.Parent4,这种形式。动态生成的数组其父类是Object类。并不是com.test.Parent4的实例,所以不在主动使用的范畴之内。
    对于数组来说,javadoc经常将构成数组的元素称为Component,实际上就是将数组降低一个维度后的类型。比如二维数组对应的就是一维数组,一维数组就对应具体的类型。反编译代码如下:

    在这里插入图片描述
    在这里插入图片描述

    扫描二维码关注公众号,回复: 9272352 查看本文章
发布了73 篇原创文章 · 获赞 91 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/weixin_42512488/article/details/89202180