《深入Java虚拟机学习笔记》- 第5章 Java虚拟机

时间:2022-08-02 22:27:35
一、JVM的生命周期
  1. 当启动一个Java程序时,一个Java虚拟机实例就诞生了;当该程序关闭退出时,这个Java虚拟机也就随之消亡;
  2. JVM实例通过调用某个初始类的main方法来运行一个Java程序;这个main方法必须是public、static的,而且返回值必须是void;任何一个拥有这样的main方法的类都可以作为Java程序运行的起点;
  3. Java程序初始类中的main方法,将作为该程序初始线程的起点,其它任何线程都是由这个初始线程启动的;
  4. 守护线程和非守护线程
    • 守护线程通常是由虚拟机自己使用的,比如执行垃圾收集任务的线程;
    • Java程序可以把它任何创建的线程标记为守护线程;
    • Java初始线程(即开始于main方法的线程)是非守护线程;
    • 只要还有任何非守护线程在运行,那么这个Java程序也在运行,即这个JVM实例还存活着;当JVM中的所有非守护线程都终止时,JVM实例将自动退出;
二、JVM的体系结构
    1. JVM体系结构如图所示
      《深入Java虚拟机学习笔记》- 第5章  Java虚拟机
      • 方法区和堆是所有线程共享的;
      • 当虚拟机装载一个class文件时,会从二进制数据中解析类型信息,然后把类型信息放到方法区中;
      • 当程序运行时,虚拟机会把所有该程序在运行时创建的对象放在堆中;如下所示:
        《深入Java虚拟机学习笔记》- 第5章  Java虚拟机
      • 当每一个新线程被创建时,将得到自己的PC寄存器和Java栈;
        如果线程正在执行的是一个Java方法,则PC寄存器的值总是指向下一条将被执行的指令;而它的Java栈则总是存储该线程中Java方法的调用状态(局部变量、参数、返回值以及中间结果);
        如果线程正在执行的是本地方法,则是以依赖于具体实现的方式存储在本地方法栈、寄存器或是其它内存区中;
      • Java栈是由许多栈桢组成的,一个栈桢包含一个方法的调用状态;当线程调用一个Java方法时,虚拟机压入一个新的栈桢到该线程的Java栈中;当该方法返回时,这个栈桢将从Java栈中弹出;
    2. 数据类型
      • Java 语言中的所有基本类型同样也都是Java虚拟机中的基本类型,但boolean有点特别,虽然Java虚拟机也把boolean当作基本类型,但是指令集 对boolean,只有很有限的支持:当编译器把Java源码编译为字节码时,它会用int或byte来表示boolean。在Java虚拟机 中,false是由整数零表示的,所有非零整数都表示true。涉及boolean的值的操作则会使用int。另外,boolean数据是当做byte数组来访问的,但是在堆区,它也可以被表示为位域;
      • Java虚拟机中还有一个只在内部使用的基本类型:returnAddress,不能在程序开发时使用这个类型,它被用来实现Java程序中的finally子句;
      • Java虚拟机有三种引用类型:
        • 类类型:对类实例的引用;
        • 接口类型:对实现了该接口的某个类实例的引用;
        • 数组类型:对数组对象的引用;在Java虚拟机中,数组是个真正的对象;
    3. 装载、连接、初始化
      • 装载:查找并装载类型的二进制数据;
      • 连接:执行验证、准备以及解析,解析是可选的;
        • 验证:确保被导入类型的正确性;
        • 准备:为类变量分配内存,并初始化为默认值;
        • 解析:把类型中的符号引用转换为直接引用;
      • 初始化:把类变量初始化为正确的初始值;
    4. 方法区
      • 当虚拟机装载某个类时,它使用类装载器定位相应的class文件,然后读入class文件中的线性二进制流,提取其中的类型信息,并将其存储到方法区;该类型中的类变量、静态变量也存储到方法区中;
      • 所有线程共享方法区,因此它们对方法区数据的访问必须设计成线程安全的;
      • 类型信息
        • 全限定名;
        • 直接超类的全限定名(如果这个类是Object类,则没有超类);
        • 是类类型还是接口类型;
        • 访问修饰符(public、abstract或final的某个子集);
        • 任何直接接口的全限定名的有限列表;
        • 该类型的常量池;
        • 字段信息;
        • 方法信息;
        • 除常量以外的所有类变量、静态变量;
        • 一个到类ClassLoader的引用;
        • 一个到Class类的引用;
      • 常量池:该类型所用常量的一个有序集合,包括直接常量和对其它类型、字段和方法的符号引用;
      • 字段信息:类型的字段,包括声明顺序都要在方法区中保存,如字段名、字段的类型以及字段的修饰符;
      • 方法信息:类型的方法,包括声明顺序都要在方法区中保存,如方法名、返回类型、参数数量,类型,顺序以及方法的修饰符;
        • 如果不是本地方法和抽象方法,还要保存方法的字节码、操作数栈和方法的栈桢中局部变量的大小以及异常表;
      • 类(静态)变量:类变量由所有类实例共享,即使没有任何类实例,它也可以被访问,因此它们总是作为类型信息的一部分保存到方法区中;
      • 指向ClassLoader类的引用:每个类被装载的时候,虚拟机必须跟踪它是由启动类装载器还是由用户自定义类装载器装载的。如果是用户自定义类装载器,则虚拟机必须在类型信息中保存对该类装载器的引用;
        • 虚拟机在动态连接期间使用这个信息,当某个类引用另一个类型时,虚拟机会请求装载发起引用类型的类装载器来装载被引用的类型;
        • 这个动态连接的过程,对于虚拟机分离命名空间也很重要;
      • 指向Class类的引用:虚拟机会为每个被装载的类型创建一个Class类的实例,并把这个实例的引用存储在方法区中;
        • 有两个方法得到类型的Class对象引用: 方法 说明 备注
          Class.forName(String className) 装载并返回该类型的Class对象引用 如果不能装载类型,则抛出ClassNotFoundException
          obj.getClass() 直接通过实例得到类型的Class对象引用  
        • 通过类型的Class对象引用,可以访问方法区中的类型信息: 方法 说明 备注
          getName() 返回类型的全限定名  
          getSuperClass() 得到直接超类 如果是Object类型或是一个接口,则返回null
          isInterface() 判断是否是接口 如果是返回true,否则返回false
          getInterfaces() 得到所有直接接口的数组 如果没有实现任何接口,返回长度为0的数组
          getClassLoader() 得到装载该类型的类装载器 如果类型是由启动类装载器装载的,则返回null
      • 方法表:为了提高访问效率,虚拟机实现中可能还包含其它数据结构来加快对原始数据的访问速度,如方法表;
        • 虚拟机会为每个装载的非抽象类生成一个方法表,并把它作为类型信息的一部分保存在方法区中;
        • 方法表是一个数组,其元素是所有它的实例可能调用的实例方法的直接引用,包括那些从超类继承过来的方法;
      • 程序计数器:每一个线程都有自己的PC寄存器,它是在线程启动时创建的;PC寄存器大小是一个字长,因此即可以持有一个本地指针,也可以持有一个returnAddress;
      • Java栈:每当启动一个新线程,虚拟机都会为它创建一个Java栈;
        • Java栈以桢为单位保存线程的运行状态;
        • 当线程调用一个Java方法时,虚拟机都会在线程的Java栈中压入一个新桢,在执行这个方法时,虚拟机使用这个栈桢存储参数、局部变量和中间运算结果等;
        • Java方法可以以两种方法完成:一种是通过return正常返回;另一种是抛出异常中止;不管是哪种返回,虚拟机都会弹出当前栈桢,丢弃;