后端精进笔记01:JVM运行的核心逻辑

本文主要讲述Java代码在JVM中是如何执行的.

一、概述:

Java代码的一生大致如下图所示,其中,JVM运行时数据区是我们需要重点关注的部分:

59

1.1 所有线程共享部分

方法区:存储JVM加载的类信息、常量、静态变量、编译后的代码等数据。在虚拟机规范中,对于方法区的定义是一个不严谨的、逻辑上的概念,实际上各虚拟机会有不同的实现。

堆内存:JVM启动时创建该区域,主要用于存储对象的实例,GC垃圾回收主要是处理堆内存。

1.2 各线程独有部分

栈内存:或称为虚拟机栈,由多个栈帧组成,一个JVM线程会执行一个或多个方法,而__一个方法对应一个栈帧__。一个栈帧包括:局部变量表、操作数栈等部分。每个线程的栈内存大小默认是1M,超出则报StackOverflowError。

本地方法栈:顾名思义,是为JVM执行native方法准备的。

程序计数器:由于CPU在单位轮片时间内,只执行一个线程的指令。每个线程的程序计数器记录当前线程执行的字节码位置,存储的是字节码的地址(如果执行的native方法,则计数器数值为空)。这样在线程切换出去再切换回来时,就能找到上次执行的指令位置,然后继续执行。

二、小试牛刀:class字节码文件内容追踪

2.1 准备工作

2.1.1 Demo.java

public class Demo01 {
    public static void main(String[] args) {
        int a = 200;
        int b = 100;
        int c = a / b;
        int d = 3;
        System.err.println(c + d);
    }
}
复制代码

2.1.2 编译(javac)并解析(javap)Demo.class

# 编译
javac Demo01.java 
# 解析。并将指令内容输出到Demo01.txt中
javap -v Demo01.class > Demo01.txt
复制代码

2.1.3 分析class文件中的指令

解析后的指令内容如下:

Classfile /Users/zephyrlai/IdeaProjects/zephyr/java-arch/src/cn/zephyr/ch1/Demo01.class
  Last modified 2020-3-10; size 429 bytes
  MD5 checksum 5c8846f77018cc43a36eeba64ff68cbd
  Compiled from "Demo01.java"
public class cn.zephyr.ch1.Demo01
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #5.#14         // java/lang/Object."<init>":()V
   #2 = Fieldref           #15.#16        // java/lang/System.err:Ljava/io/PrintStream;
   #3 = Methodref          #17.#18        // java/io/PrintStream.println:(I)V
   #4 = Class              #19            // cn/zephyr/ch1/Demo01
   #5 = Class              #20            // java/lang/Object
   #6 = Utf8               <init>
   #7 = Utf8               ()V
   #8 = Utf8               Code
   #9 = Utf8               LineNumberTable
  #10 = Utf8               main
  #11 = Utf8               ([Ljava/lang/String;)V
  #12 = Utf8               SourceFile
  #13 = Utf8               Demo01.java
  #14 = NameAndType        #6:#7          // "<init>":()V
  #15 = Class              #21            // java/lang/System
  #16 = NameAndType        #22:#23        // err:Ljava/io/PrintStream;
  #17 = Class              #24            // java/io/PrintStream
  #18 = NameAndType        #25:#26        // println:(I)V
  #19 = Utf8               cn/zephyr/ch1/Demo01
  #20 = Utf8               java/lang/Object
  #21 = Utf8               java/lang/System
  #22 = Utf8               err
  #23 = Utf8               Ljava/io/PrintStream;
  #24 = Utf8               java/io/PrintStream
  #25 = Utf8               println
  #26 = Utf8               (I)V
{
  public cn.zephyr.ch1.Demo01();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 9: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=5, args_size=1
         0: bipush        200
         2: istore_1
         3: sipush        100
         6: istore_2
         7: iload_1
         8: iload_2
         9: idiv
        10: istore_3
        11: iconst_3
        12: istore        4
        14: getstatic     #2                  // Field java/lang/System.err:Ljava/io/PrintStream;
        17: iload_3
        18: iload         4
        20: iadd
        21: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
        24: return
      LineNumberTable:
        line 11: 0
        line 12: 3
        line 13: 7
        line 14: 11
        line 15: 14
        line 16: 24
}
SourceFile: "Demo01.java"
复制代码

可以看到大致可以分为如下4块

  • 前4行:无关代码内容的描述信息

  • 5~8行:版本号、访问标志

    • (major-verion)版本号:jdk5、6、7、8分别是49、50、51、52

    • 访问标志:

      标志名称 标志值 含义
      ACC_PUBLIC 0x0001 标记为public类型
      ACC_FINAL 0x0010 标记被声明为final(仅类能设置)
      ACC_SUPER 0x0020 jdk1.2之后为true(是否使用invokespecial字节码指令)
      ACC_INTERFACE 0x0200 标记为接口
      ACC_ABSTRACT 0x0400 标记为abstract类型(对于接口、抽象类则为true)
      ACC_SYNTHETIC 0x1000 标记这个类不是用户产生的
      ACC_ANNOTATION 0x2000 标记为注解类
      ACC_ENUM 0x4000 标记为枚举类
  • 以Constant pool为起点的常量池(区别于String常量池):主要存储类信息中包含的静态常量,即编译当前类之后,所用到的常量。例如类名、方法名等。常见的常量类型如下:

  • {}包裹的业务代码,而这块业务代码包含了2块:

    • 默认无参构造器
    • main方法
      • flags:依旧是访问控制
      • Code下的stack=3, locals=5, args_size=1则分别表示本地变量数量为3、参数数量为5、(方法对应的)栈帧中操作数栈的深度为1
      • 剩下的即为业务代码

2.2 程序运行分析

2.2.1 加载class文件

主要加载各种类信息、以及类中用到的常量、静态变量等,存储在方法区。创建类的实例,存储在堆内存中。

2.2.2 JVM创建线程来执行代码

开辟各线程独立的虚拟机栈(包括多个栈帧)、程序计数器等内存空间

2.2.3 字节码执行分析

参考

JVM 虚拟机字节码指令表

猜你喜欢

转载自juejin.im/post/5e67c04251882549724e3770