开新坑!JVM系列组合拳吊打面试官

100 阅读8分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

JVM的概览

为何要了解JVM

  • The Java Virtual Machine is the cornerstone of the Java platform.
  • The Java Virtual Machine is an abstract computing machine. Like a real computing machine, it has an instruction set and manipulates various memory areas at run time.
  • The Java Virtual Machine knows nothing of the Java programming language, only of a particular binary format, the class file format.

JVM,JDK,JRE的关系

developer guider

image-20220509100602490.png

由图可知,JDK包含JRE,JRE包含了JVM。

关于语言

语言类型定义特点示例
编译型使用特定的编译器,针对不同的平台,将源代码一次性的编译成该平台可执行的程序格式执行速度快,效率高;
依靠编译器,跨平台差
C,C++,GoLang
解释型使用特定的解释器对源程序逐行解释成平台机器码并立即执行,在执行时才被一行一行解释。执行速度慢,效率低;
依靠解释器,跨平台友好
Python,JavaScript
编译型&解释型由javac等编译器编译成JVM能够识别的文件格式,再由JVM解释或编译成具体的平台执行的程序格式执行速度快,效率高,跨平台友好Java

关于冯诺依曼

既然Java官方将JVM称为一个抽象计算机,那么肯定满足冯诺依曼的体系,在JVM的探索中我们期望可以将JVM的相关结构进行验证。

冯诺依曼结构JVM中结构备注
Input类加载器加载class文件
Output平台适配的机器码
Memory分割成多个区域的存储区,运行时数据区

Von_Neumann_architecture.svg.png

JVM类加载过程

编译(从.java到.class)

词法分析器\Longrightarrow语法分析器\Longrightarrow语义分析器\Longrightarrow代码生成器

Java源码通过词法分析器生成token流token流通过语法分析器生成语法树;通过语义分析器生成注解语法树;再通过代码生成器生成字节码文件

class文件解析

Demo

package com.bayitalk.clazz;

/**
 * <p> 测试类文件格式
 *
 * @author Morris
 * @version 1.0
 */
public class TClass {
    private final static int a = 20;

    private static int b = 129;

    private int c = 12;

    private static String comment = "This is Test class";

    public String getComment(){
        return comment;
    }

    public static void main(String[] args) {
        System.out.println(a);
    }

}

javac编译成class

image-20220509131610671.png

class文件格式

Chapter 4. The class File Format

ClassFile {
    u4             magic;
    u2             minor_version;
    u2             major_version;
    u2             constant_pool_count;
    cp_info        constant_pool[constant_pool_count-1];
    u2             access_flags;
    u2             this_class;
    u2             super_class;
    u2             interfaces_count;
    u2             interfaces[interfaces_count];
    u2             fields_count;
    field_info     fields[fields_count];
    u2             methods_count;
    method_info    methods[methods_count];
    u2             attributes_count;
    attribute_info attributes[attributes_count];
}

例如magic的u4中:u表示无符号,4表示4个字节,在十六进制中,两个数字表示1个字节。

structure大小含义备注
magicu4魔法标识,用来标识class文件格式0xCAFBABE是固定值
minor_versionu2java的小版本号
major_versionu2java的大版本号52是java8
constant_pool_countu2常量池中常量的个数由于下标是从1开始计算的,因此实际个数=count-1
constant_pool[constant_pool_count-1];cp_info常量池具体内容
access_flagsu2访问标识

javap 反编译

javap -v -p TClass.class > TClass.txt
Classfile /Users/huxingbo/Documents/morris_projects/jvm-stu/src/main/java/com/bayitalk/clazz/TClass.class
  Last modified 2022-5-9; size 717 bytes
  MD5 checksum 19506192bfa2c2d0aeb1fd37bdb5db45
  Compiled from "TClass.java"
public class com.bayitalk.clazz.TClass
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #9.#29         // java/lang/Object."<init>":()V
   #2 = Fieldref           #5.#30         // com/bayitalk/clazz/TClass.c:I
   #3 = Fieldref           #5.#31         // com/bayitalk/clazz/TClass.comment:Ljava/lang/String;
   #4 = Fieldref           #32.#33        // java/lang/System.out:Ljava/io/PrintStream;
   #5 = Class              #34            // com/bayitalk/clazz/TClass
   #6 = Methodref          #35.#36        // java/io/PrintStream.println:(I)V
   #7 = Fieldref           #5.#37         // com/bayitalk/clazz/TClass.b:I
   #8 = String             #38            // This is Test class
   #9 = Class              #39            // java/lang/Object
  #10 = Utf8               a
  #11 = Utf8               I
  #12 = Utf8               ConstantValue
  #13 = Integer            20
  #14 = Utf8               b
  #15 = Utf8               c
  #16 = Utf8               comment
  #17 = Utf8               Ljava/lang/String;
  #18 = Utf8               <init>
  #19 = Utf8               ()V
  #20 = Utf8               Code
  #21 = Utf8               LineNumberTable
  #22 = Utf8               getComment
  #23 = Utf8               ()Ljava/lang/String;
  #24 = Utf8               main
  #25 = Utf8               ([Ljava/lang/String;)V
  #26 = Utf8               <clinit>
  #27 = Utf8               SourceFile
  #28 = Utf8               TClass.java
  #29 = NameAndType        #18:#19        // "<init>":()V
  #30 = NameAndType        #15:#11        // c:I
  #31 = NameAndType        #16:#17        // comment:Ljava/lang/String;
  #32 = Class              #40            // java/lang/System
  #33 = NameAndType        #41:#42        // out:Ljava/io/PrintStream;
  #34 = Utf8               com/bayitalk/clazz/TClass
  #35 = Class              #43            // java/io/PrintStream
  #36 = NameAndType        #44:#45        // println:(I)V
  #37 = NameAndType        #14:#11        // b:I
  #38 = Utf8               This is Test class
  #39 = Utf8               java/lang/Object
  #40 = Utf8               java/lang/System
  #41 = Utf8               out
  #42 = Utf8               Ljava/io/PrintStream;
  #43 = Utf8               java/io/PrintStream
  #44 = Utf8               println
  #45 = Utf8               (I)V
{
  private static final int a;
    descriptor: I
    flags: ACC_PRIVATE, ACC_STATIC, ACC_FINAL
    ConstantValue: int 20

  private static int b;
    descriptor: I
    flags: ACC_PRIVATE, ACC_STATIC

  private int c;
    descriptor: I
    flags: ACC_PRIVATE

  private static java.lang.String comment;
    descriptor: Ljava/lang/String;
    flags: ACC_PRIVATE, ACC_STATIC

  public com.bayitalk.clazz.TClass();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: bipush        12
         7: putfield      #2                  // Field c:I
        10: return
      LineNumberTable:
        line 9: 0
        line 14: 4

  public java.lang.String getComment();
    descriptor: ()Ljava/lang/String;
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: getstatic     #3                  // Field comment:Ljava/lang/String;
         3: areturn
      LineNumberTable:
        line 19: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: bipush        20
         5: invokevirtual #6                  // Method java/io/PrintStream.println:(I)V
         8: return
      LineNumberTable:
        line 23: 0
        line 24: 8

  static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=1, locals=0, args_size=0
         0: sipush        129
         3: putstatic     #7                  // Field b:I
         6: ldc           #8                  // String This is Test class
         8: putstatic     #3                  // Field comment:Ljava/lang/String;
        11: return
      LineNumberTable:
        line 12: 0
        line 16: 6
}
SourceFile: "TClass.java"

加载(从class到jvm)

Chapter 5. Loading, Linking, and Initializing

加载.class的方式

加载方式示例备注
本地加载
网络加载Web Applet,小程序
归档文件加载jar,war
数据库加载JSP从数据库提取class文件少见
动态编译,运行时计算动态代理
加密文件防class反编译

类生命周期:装载(Load)\Longrightarrow 链接(Link)\Longrightarrow初始化(Initialize) \Longrightarrow使用(Use)\Longrightarrow卸载(Upload)

类加载机制:虚拟机将.class文件加载到内存\Longrightarrow对数据进行校验、转换、解析、初始化\Longrightarrow变成虚拟机可以直接使用的java类型:java.lang.Class

装载(Load)

  • 通过类加载器(寻找器,独立于JVM之外的实现,可以决定如何加载)获取类的二进制流,一般而言,Java程序员能够在这一步通过自定义加载器,字节码增强,java agent方式来增强类

  • 字节流中的静态存储结构会转化成方法区的运行时数据结构,分配内存(此时内存中有数据位置,但是无法访问)

  • 在java堆中生成被加载类的java.lang.Class对象作为数据访问入口

  • JIT的热点代码不再这个阶段进入方法区

位置数据
方法区类信息、常量、静态变量
java.lang.Class作为数据的访问入口

链接(Link)

验证(Verification)

验证贯穿着整个类加载周期,以保证虚拟机不受危害

验证详情备注
文件格式的验证1.文件数据结构格式
2.版本,保证字节流能被正确存储方法区
元数据验证语义验证,java语法,方法区的存储验证
字节码验证1.数据流控制流的分析验证
2.类的方法体,安全性验证
3.栈数据类型操作码合法性检查,自身大小,不是深度
4.字节码跳转是否合法(例如常量池的引用跳转)
符号引用验证符号引用转变为直接引用的合法性
准备(Preparation)
  • 为静态变量分配内存,初始化默认值,并不执行Java代码。默认值而非初始值。
  • final static修饰的在准备期间会初始化,被ConstantValue修饰的变量,会从常量池中找值进行赋值。常量池中只有基础数据类型和String类型
  • 准备阶段只需要在初始化之前就可以发生。
解析(Resolution)

Java 虚拟机指令 anewarraycheckcastgetfieldgetstaticinstanceofinvokedynamicinvokeinterfaceinvokespecialinvokestaticinvokevirtualldcldc_wmultianewarraynewputfieldputstatic对运行时常量池进行符号引用。执行任何这些指令都需要解析其符号引用。

  • 解析是从运行时常量池中的符号引用动态确定具体值的过程。

  • 由于同一个符号引用可能被多次解析,因此JVM会对解析的结果进行缓存。除了invokedynamic指令,后续被用到java8的lambda表达式中。

  • Overriding也与此阶段相关

初始化( Initialization)

  • 执行类构造器的方法,赋初始值,声明类变量指定初始值
  • 静态代码块为类变量赋值
  • 如果没有Load,Link,则先Load、Link
  • 如果该类父类还没有初始化,先初始化父类
  • 依次执行初始化语句

JVM是多线程进行初始化的,使用初始化锁LC控制。

使用(Use)

只有主动引用了类,才会初始化类。

主动引用
  • 创建类的时候,new
  • 访问或使用接口和类的静态变量
  • 调用类的静态方法
  • 反射
  • 初始化子类时,先初始化父类
  • 启动类
被动引用
  • 引用父类的静态字段,只初始化父类,子类不出适合
  • 定义类数组,不会类初始化
  • 引用类static和final同时修饰的常量,不会引起类初始化

卸载

  • 类的所有实例被回收
  • 加载类的ClassLoader被回收
  • 该类的java.lang.Class对象没有被引用