JVM 面试基础准备篇(一)

原创
2020/09/28 00:09
阅读数 9

JVM 面试基础准备篇(一)

1. 计算机原理

    • 1.1  计算机体系结构

    • 1.2 JVM 作用

  • 2. JVM 原理

    • 2.1  Class文件

    • 2.2 类文件到虚拟机

    • 2.2.3 初始化(Initialize)

    • 2.2.4 类加载器 classLoader

  • 小结


今天主要分享 JVM 的基础知识。

  • JVM 作用
  • 类装载机制
  • 类文件结构

1. 计算机原理

1.1  计算机体系结构

遵循冯诺依曼计算机结构


计算机处理数据过程

  • 提取阶段: 由输入设备把原始数据或信息输入给计算机存储器存起来

  • 解码阶段: 根据CPU的指令集架构(ISA)定义将数值解译为指令

  • 执行阶段: 再由控制器把需要处理或计算的数据调入运算器

  • 最终阶段: 由输出设备把最后运算结果输出

本质上就是CPU取数据指令然后返回

CPU=存储器+运算器+控制器

1.2 JVM 作用

.

机器语言

我们把CPU能够直接认识的数据指令,称为机器语言,也就是010101001这种形式

不同厂商的 CPU

  • 单核、双核、多核

  • Intel、AMD、IBM等

不同 CPU 使用的 CPU 指令集是不一样的,这就会有不兼容的问题;而且要是直接操作 01 这种形式的,非常麻烦并且容易出错,硬件资源管理起来也不方便。

操作系统

  • 向下对接指令系统、管理硬件资源
  • 向上提供给用户简单的操作命令和界面

2. JVM 原理

官方文档

JDK Reference

JDK8 JVM Reference


2.1  Class文件

2.1.1 生成字节码文件

package com.example.jvm.clazz;

class Person {
    private String name = "Jack";
    private int age;
    private final double salary = 100;
    private static String address;
    private final static String hobby = "Programming";
    private static Object obj = new Object();

    public void say() {
        System.out.println("person say...");
    }

    public static int calc(int op1, int op2) {
        op1 = 3;
        int result = op1 + op2;
        Object obj = new Object();
        return result;
    }

    public static void main(String[] args) {
        calc(12);
    }
}

编译生成 class 文件:javac -g:vars Person.java


vim Person.class


前期编译

源文件Person.java

  • 词法分析器
  • tokens流
  • 语法分析器
  • 语法树/抽象语法树
  • 语义分析器
  • 注解抽象语法树
  • 字节码生成器
  • Person.class 文件

2.1.2 查看字节码文件

vim Person.class ,然后输入:%!xxd 就是以16进制显示class文件了,内容如下:


Tips

linux下查看二进制文件
以十六进制格式输出:
od [选项] 文件
od -d 文件 十进制输出
-o 文件 八进制输出
-x 文件 十六进制输出
xxd 文件 输出十六进制

在vi命令状态下:
:%!xxd :%!od 将当前文本转化为16进制格式
:%!xxd -c 12 每行显示12个字节
:%!xxd -r 将当前文本转化回文本格式

2.1.3 类文件结构

官网说明:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html

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];
}

分析

  • u4             magic;

    The magic item supplies the magic number identifying the class file format; it has the value 0xCAFEBABE.

    提供了一个魔法值来声明类文件格式,对应值为cafe babe

  • u2             minor_version;

  • u2             major_version;

0000 0034 16进制的,对应10进制:16*3+4=52,标识 JDK8

  • u2             constant_pool_count;

The value of the constant_pool_count item is equal to the number of entries in the constant_pool table plus one. A constant_pool index is considered valid if it is greater than zero and less than constant_pool_count, with the exception for constants of type long and double noted in §4.4.5.

003f 表示有 3*16+15=62 个元素在常量池中(下标区间[1,constant_pool_count-1])。

  • cp_info        constant_pool[constant_pool_count-1];

    The constant_pool is a table of structures (§4.4) representing various string constants, class and interface names, field names, and other constants that are referred to within the ClassFile structure and its substructures. The format of each constant_pool table entry is indicated by its first "tag" byte.

    The constant_pool table is indexed from 1 to constant_pool_count - 1.

    常量池主要存储两方面内容:字面量(Literal)和符号引用(Symbolic References)

    字面量:文本字符串,final修饰等符号引用:类和接口的全限定名、字段名称和描述符、方法名称和描述符

2.1.3.1  javap 验证

javap -v -p Person.class > vp.txt 反编译验证字节码和指令信息

// 打开 vp.txt
Classfile /Users/xiazhaoyang/Ashe/workspace/architectrue-adventure/code-examples/jdk-jvm-analysis/src/main/java/com/example/jvm/clazz/Person.class
Last modified 2020-3-31; size 982 bytes
MD5 checksum de6394397e12fac0f518b4de12f6cef9 // 魔法值
class com.example.jvm.clazz.Person
minor version: 0
major version: 52 // JDK 版本号
flagsACC_SUPER
Constant pool: // 常量池
 #1 
= Methodref          #10.#43        // java/lang/Object."<init>":()V
 #2 = String             #44            // Jack
 #3 = Fieldref           #13.#45        // com/example/jvm/clazz/Person.name:Ljava/lang/String;
 #4 = Double             100.0d
 #6 = Fieldref           #13.#46        // com/example/jvm/clazz/Person.salary:D
 #7 = Fieldref           #47.#48        // java/lang/System.out:Ljava/io/PrintStream;
 #8 = String             #49            // person say...
 #9 = Methodref          #50.#51        // java/io/PrintStream.println:(Ljava/lang/String;)V
#10 = Class              #52            // java/lang/Object
#11 = Methodref          #13.#53        // com/example/jvm/clazz/Person.calc:(II)I
#12 = Fieldref           #13.#54        // com/example/jvm/clazz/Person.obj:Ljava/lang/Object;
#13 = Class              #55            // com/example/jvm/clazz/Person
#14 = Utf8               name
#15 = Utf8               Ljava/lang/String;
#16 = Utf8               age
#17 = Utf8               I
#18 = Utf8               salary
#19 = Utf8               D
#20 = Utf8               ConstantValue
#21 = Utf8               address
#22 = Utf8               hobby
#23 = String             #56            // Programming
#24 = Utf8               obj
#25 = Utf8               Ljava/lang/Object;
#26 = Utf8               <init>
#27 = Utf8               ()V
#28 = Utf8               Code
#29 = Utf8               LocalVariableTable
#30 = Utf8               this
#31 = Utf8               Lcom/example/jvm/clazz/Person;
#32 = Utf8               say
#33 = Utf8               calc
#34 = Utf8               (II)I
#35 = Utf8               op1
#36 = Utf8               op2
#37 = Utf8               result
#38 = Utf8               main
#39 = Utf8               ([Ljava/lang/String;)V
#40 = Utf8               args
#41 = Utf8               [Ljava/lang/String;
#42 = Utf8               <clinit>
#43 = NameAndType        #26:#27        // "<init>":()V
#44 = Utf8               Jack
#45 = NameAndType        #14:#15        // name:Ljava/lang/String;
#46 = NameAndType        #18:#19        // salary:D
#47 = Class              #57            // java/lang/System
#48 = NameAndType        #58:#59        // out:Ljava/io/PrintStream;
#49 = Utf8               person say...
#50 = Class              #60            // java/io/PrintStream
#51 = NameAndType        #61:#62        // println:(Ljava/lang/String;)V
#52 = Utf8               java/lang/Object
#53 = NameAndType        #33:#34        // calc:(II)I
#54 = NameAndType        #24:#25        // obj:Ljava/lang/Object;
#55 = Utf8               com/example/jvm/clazz/Person
#56 = Utf8               Programming
#57 = Utf8               java/lang/System
#58 = Utf8               out
#59 = Utf8               Ljava/io/PrintStream;
#60 = Utf8               java/io/PrintStream
#61 = Utf8               println
#62 = Utf8               (Ljava/lang/String;)V
// 字段表集合                        
{
private java.lang.String name;
  descriptor: Ljava/lang/String;
  flags: ACC_PRIVATE

private int age;
  descriptor: I
  flags: ACC_PRIVATE

private final double salary;
  descriptor: D
  flags: ACC_PRIVATE, ACC_FINAL
  ConstantValue: double 100.0d

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

private static final java.lang.String hobby;
  descriptor: Ljava/lang/String;
  flags: ACC_PRIVATE, ACC_STATIC, ACC_FINAL
  ConstantValue: String Programming

private static java.lang.Object obj;
  descriptor: Ljava/lang/Object;
  flags: ACC_PRIVATE, ACC_STATIC
  // 方法表集合
com.example.jvm.clazz.Person();
  descriptor: ()V
  flags:
  Code:
    stack=3, locals=1, args_size=1
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: aload_0
       5: ldc           #2                  // String Jack
       7: putfield      #3                  // Field name:Ljava/lang/String;
      10: aload_0
      11: ldc2_w        #4                  // double 100.0d
      14: putfield      #6                  // Field salary:D
      17return
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0      18     0  this   Lcom/example/jvm/clazz/Person;

public void say();
  descriptor: ()V
  flags: ACC_PUBLIC
  Code:
    stack=2, locals=1, args_size=1
       0: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: ldc           #8                  // String person say...
       5: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       8return
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0       9     0  this   Lcom/example/jvm/clazz/Person;

public static int calc(intint);
  descriptor: (II)I
  flags: ACC_PUBLIC, ACC_STATIC
  Code:
    stack=2, locals=4, args_size=2
       0: iconst_3
       1: istore_0
       2: iload_0
       3: iload_1
       4: iadd
       5: istore_2
       6new           #10                 // class java/lang/Object
       9: dup
      10: invokespecial #1                  // Method java/lang/Object."<init>":()V
      13: astore_3
      14: iload_2
      15: ireturn
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0      16     0   op1   I
          0      16     1   op2   I
          6      10     2 result   I
         14       2     3   obj   Ljava/lang/Object;

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: iconst_1
       1: iconst_2
       2: invokestatic  #11                 // Method calc:(II)I
       5: pop
       6return
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0       7     0  args   [Ljava/lang/String;

static {};
  descriptor: ()V
  flags: ACC_STATIC
  Code:
    stack=2, locals=0, args_size=0
       0new           #10                 // class java/lang/Object
       3: dup
       4: invokespecial #1                  // Method java/lang/Object."<init>":()V
       7: putstatic     #12                 // Field obj:Ljava/lang/Object;
      10return
}
2.1.3.2 常量池分析

官网:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.4

上面分析到常量池中常量的数量是62,接下来我们来具体分析一下这62个常量:

  • cp_info constant_pool[constant_pool_count-1] 也就是这块包括的信息 ,

  • cp_info 其实就是一个表格的形式 All constant_pool table entries have the following general format:

cp_info {
    u1 tag;
    u1 info[];
}

常量池标识符

Constant Type Value
CONSTANT_Class 7
CONSTANT_Fieldref 9
CONSTANT_Methodref 10
CONSTANT_InterfaceMethodref 11
CONSTANT_String 8
CONSTANT_Integer 3
CONSTANT_Float 4
CONSTANT_Long 5
CONSTANT_Double 6
CONSTANT_NameAndType 12
CONSTANT_Utf8 1
CONSTANT_MethodHandle 15
CONSTANT_MethodType 16
CONSTANT_InvokeDynamic 18

常量池中的每一个元素必须以 1 byte 的标识符号作为开始,以此来表示它的具体 cp_info 类型。上表中列出的为官方声明合法的标识符。

00000000: cafe babe 0000 0034 003f -> 0a00 0a00 2b08  .......4.?....+.
000000100020900 0d00 2d06 4059 0000 0000 0000  .,....-.@Y......
  • 接着往后面数一个 u1: 0a,表示10,对应 CONSTANT_Methodref ,表示这是一个方法引用。
CONSTANT_Methodref_info {
    u1 tag;
    u2 class_index;
    u2 name_and_type_index;
}

往下数一个 u1: 00 标示起始位,对应其具体的 cp_info 类型

往下数两个 u2

00 0a 对应 10, 代表的是class_index,表示该方法所属的类在常量池中的索引

00 2b对应 32+11=43,代表的是name_and_type_index,表示该方法的名称和类型的索引

#1 = Methodref          #10.#43        // java/lang/Object."<init>":()V
  • 往下数一个 u1 是 08,表示 CONSTANT_String
CONSTANT_String_info {
    u1 tag;
    u2 string_index;
}

根据之前的规律,第一个 u1 表示类型,往后数一个 u2 表示对应存储指向的索引位置

00000000: cafe babe 0000 0034 003f 0a00 0a00 2b08  .......4.?....+.
00000010: -> 002c 0900 0d00 2d06 4059 0000 0000 0000  .,....-.@Y......

002c = 32 + 12 = 44;

#2 = String             #44            // Jack

后续的依此类推,简而言之,Java 通过这种方式来表述一个类文件的结构。

2.1.3.3 结构划分
  • 文件头信息:含 majic、版本信息
  • 常量池定义
  • 字段表集合
  • 方法表集合

2.2 类文件到虚拟机

所谓类加载机制就是

  • 虚拟机把Class文件加载到内存
  • 并对数据进行校验
  • 转换解析和初始化形成可以虚拟机直接使用的Java 类型,即 java.lang.Class

2.2.1 装载

查找和导入class文件

  • 通过一个类的全限定名获取定义此类的二进制字节流
  • 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
  • 在Java堆中生成一个代表这个类的 java.lang.Class 对象,作为对方法区中这些数据的访问入口

Class 对象封装了类在方法区内的数据结构,并且向 Java 程序员提供了访问方法区内的数据结构的接口。在 Java 堆中生成一个代表这个类的 java.lang.Class 对象,作为对方法区中这些数据的访问入口:


2.2.2 链接(Link)

2.2.2.1 验证(verify)

保证被加载类的正确性

  • 文件格式验证
  • 元数据验证
  • 字节码验证
  • 符号引用验证
2.2.2.2 准备(prepare)

为类的静态变量分配内存,并将其初始化为默认值

graph TD
A[方法区]
A-->A1[静态变量的初始化 int age=10]
A1 --> B[堆]


public class Demo1 {
    private static int i;
    public static void main(String[] args) {
     // 正常打印出0,因为静态变量i在准备阶段会有默认值0 
      System.out.println(i);
    } 
}
public class Demo2 {
    public static void main(String[] args) {
      // 编译通不过,因为局部变量没有赋值不能被使用 int i;
    System.out.println(i);
    } 
}
2.2.2.3 解析(resolve)

将前文分析的类文件结构中的符号引用转换为直接引用

符号引用就是一组符号来描述目标,可以是任何字面量。
直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。

解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用限定符7类符号引用进行。

2.2.3 初始化(Initialize)

对类的静态变量,静态代码块执行初始化操作

graph TD
A[方法区]
A-->A1[将类中的符号引用转换为直接引用]
A1 --> B[堆]

2.2.4 类加载器 classLoader

在装载 (Load) 阶段,其中第(1)步:通过类的全限定名获取其定义的二进制字节流,需要借助类装载器完成,顾名思义,就是用来装载Class 文件的。

2.2.4.1 分类

  • Bootstrap ClassLoader 负责加载$JAVA_HOME中 jre/lib/rt.jar 里所有的class或 Xbootclassoath选项指定的jar包。由C++实现, 不是ClassLoader子类
  • Extension ClassLoader 负责加载java平台中扩展功能的一些jar包,包括$JAVA_HOME中 jre/lib/*.jar 或 -Djava.ext.dirs指定目录下的jar包。
  • App ClassLoader 负责加载classpath中指定的jar包及 Djava.class.path 所指定目录下的类和 jar包。
  • Custom ClassLoader 通过 java.lang.ClassLoader 的子类自定义加载 class,属于应用程序根据 自身需要自定义的 ClassLoader,如 tomcat、jboss 都会根据 j2ee 规范自行实现 ClassLoader。

2.2.4.2 图解


public class ClassLoaderTest {

        public static void main(String[] args) {
            // App ClassLoader
            System.out.println(new ClassLoaderTest().getClass().getClassLoader());
            // Ext ClassLoader
            System.out.println(new ClassLoaderTest().getClass().getClassLoader().getParent());
            // Bootstrap ClassLoader
            System.out.println(new ClassLoaderTest().getClass().getClassLoader().getParent().getParent());
            System.out.println(new String().getClass().getClassLoader());
        }
}

输出

sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@59690aa4
null
null

2.2.4.3 类加载原则(双亲委派)

  • 检查某个类是否已经加载

自底向上,从Custom ClassLoader到BootStrap ClassLoader逐层检查,只要某个Classloader已加 载,就视为已加载此类,保证此类只所有ClassLoader加载一次。

  • 加载的顺序 自顶向下,也就是由上层来逐层尝试加载此类。
protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                   // 如果父加载器存在,委托给父类加载器加载
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

2.2.4.4 自定义加载器(破坏双亲委派)

  • Tomcat

  • SPI 机制
  • OSGi

小结

JVM 文件结构是理解 JVM 运行时数据区和内存模型的必备知识,简单总结下:

  • JVM 使用的是双亲委派的加载机制
  • 类文件要经过装载、链接、初始化后才能在虚拟机中被使用
  • Class 文件中定义了文件头、常量池、字段表和方法表


本文分享自微信公众号 - 架构探险之道(zacsnz1314)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

展开阅读全文
加载中

作者的其它热门文章

打赏
0
0 收藏
分享
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部