1. 先来看看JVM运行时数据区的结构

java 运行进程 .java运行_编程语言

  • 线程独占: 每个线程都有它独立的空间,随线程生命周期而创建和销毁。
  • 线程共享: 所有线程能访问这块内存数据,随虚拟机GC 而创建和销毁。
方法区
  • JVM 用来存储加载的类信息、常量、静态变量、编译后的代码等数据。
  • 虚拟机规范中,这是一个逻辑区域。
  • 具体实现根据不同虚拟机来实现。
  • 如 oracle 的 HotSpot 在 java7 中方法区放在永久代,java8 放在元数据空间,并且通过 GC 机制对这个区域进行管理。
堆内存

java 运行进程 .java运行_java 运行进程_02

  • 堆内存可以分为:
  • 老年代
  • 新生代
  • Eden
  • From Survivor
  • To Survivor
  • JVM 启动时创建,存放对象的实例。
  • 垃圾回收期主要就是管理堆内存。如果满了,就会出现 OutOfMemoryError
虚拟机栈
  • 每个线程在这个空间有一个私有的空间。
  • 线程栈由多个栈帧(Stack Frame)组成。
  • 一个线程会执行一个或多个方法,一个方法对应一个栈帧。
  • 栈帧内容包含: 局部变量表、操作数栈、动态链接、方法返回地址、附加信息等。
  • 栈内存默认最大是 1M,超出则抛出 StackOverflowError
本地方法栈
  • 和虚拟机栈功能类似,虚拟机栈是为虚拟机执行 JAVA 方法而准备的,本地方法栈是为虚拟机使用 Native 本地方法而准备的。
  • 虚拟机规范没有规定具体的实现,由不同的虚拟机厂商去实现。
  • HotSpot 虚拟机中虚拟机栈和本地方法栈的实现是一样的。同样,超出大小以后也会抛出 StackOverflowError
程序计数器(Program Counter Register)
  • 记录当前线程执行字节码的位置,存储的是字节码指令地址,如果执行 Native 方法,则计数器值为空。
  • 每个线程都在这个空间有一个私有的空间,占用内存空间很少。
  • CPU 同一时间,只会执行一条线程中的指令。JVM 多线程会轮流切换并分配 CPU 执行时间的方式。为了线程切换后,需要通过程序计数器,来恢复正确的执行位置。

2. 接下来看看我们经常提到的字节码文件吧

1. 先搞一个测试代码
public class Demo1 {
    public static void main(String[] args) {
        int x = 500;
        int y = 100;
        int a = x / y;
        int b = 50;
        System.out.println(a + b);
    }
}
2. 编译并生成class文件
# 编译
javac Demo1.java
# 查看文件内容
javap -v Demo1.class > Demo.txt
3. 接下来看看Demo.txt文件都有些什么吧

针对 class 文件的官方描述(https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.1)

Classfile /Users/shadowolf/Demo1.class
  Last modified 2019-11-7; size 414 bytes
  MD5 checksum ae6fa820973681b35609c75631cb255b
  Compiled from "Demo1.java"
public class Demo1
  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.out:Ljava/io/PrintStream;
   #3 = Methodref          #17.#18        // java/io/PrintStream.println:(I)V
   #4 = Class              #19            // Demo1
   #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               Demo1.java
  #14 = NameAndType        #6:#7          // "<init>":()V
  #15 = Class              #21            // java/lang/System
  #16 = NameAndType        #22:#23        // out:Ljava/io/PrintStream;
  #17 = Class              #24            // java/io/PrintStream
  #18 = NameAndType        #25:#26        // println:(I)V
  #19 = Utf8               Demo1
  #20 = Utf8               java/lang/Object
  #21 = Utf8               java/lang/System
  #22 = Utf8               out
  #23 = Utf8               Ljava/io/PrintStream;
  #24 = Utf8               java/io/PrintStream
  #25 = Utf8               println
  #26 = Utf8               (I)V
{
  public Demo1();
    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 1: 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: sipush        500
         3: istore_1
         4: bipush        100
         6: istore_2
         7: iload_1
         8: iload_2
         9: idiv
        10: istore_3
        11: bipush        50
        13: istore        4
        15: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
        18: iload_3
        19: iload         4
        21: iadd
        22: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
        25: return
      LineNumberTable:
        line 3: 0
        line 4: 4
        line 5: 7
        line 6: 11
        line 7: 15
        line 8: 25
}
SourceFile: "Demo1.java"
Classfile
Classfile /Users/shadowolf/Demo1.class
  Last modified 2019-11-7; size 414 bytes
  MD5 checksum ae6fa820973681b35609c75631cb255b
  Compiled from "Demo1.java"
  • 主要记录了一些文件的信息,包括文件本地地址、文件大小、最后更新时间、MD5校验、编译来源等。
public class Demo1
public class Demo1
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
  • 这一块主要描述编译的一些信息。
  • major version: 主版本号,minor version: 次版本号,以下是版本的对应关系。

JDK版本

major.minor version

1.1

45

1.2

46

1.3

47

1.4

48

1.5

49

1.6

50

1.7

51

1.8

52

  • 剩下的自己往下计算便可。
  • flags: 访问标志。如下是访问标志列表及解释

标志名称

标志值

含义

ACC_PUBLIC

0x0001

是否为 public 类型

ACC_FINAL

0x0010

是否被声明为 final,只有类可设置

ACC_SUPER

0x0020

是否允许使用 invokespecial 字节码指令,JDK12 之后编译出来的类的这个标示为 true

ACC_INTERFACE

0x0200

标志这个是一个接口

ACC_ABSTRACT

0x0400

是否为 abstract 类型,对于接口或抽象类来说,此标志值为 true,其他值为 false

ACC_SYNTHETIC

0x1000

标志这个类并非️用户产生的

ACC_ANNOTATION

0x2000

标识这是一个注解

ACC_ENUM

0x4000

标识这是一个枚举

Constant pool
Constant pool:
   #1 = Methodref          #5.#14         // java/lang/Object."<init>":()V
   #2 = Fieldref           #15.#16        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = Methodref          #17.#18        // java/io/PrintStream.println:(I)V
   #4 = Class              #19            // Demo1
   #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               Demo1.java
  #14 = NameAndType        #6:#7          // "<init>":()V
  #15 = Class              #21            // java/lang/System
  #16 = NameAndType        #22:#23        // out:Ljava/io/PrintStream;
  #17 = Class              #24            // java/io/PrintStream
  #18 = NameAndType        #25:#26        // println:(I)V
  #19 = Utf8               Demo1
  #20 = Utf8               java/lang/Object
  #21 = Utf8               java/lang/System
  #22 = Utf8               out
  #23 = Utf8               Ljava/io/PrintStream;
  #24 = Utf8               java/io/PrintStream
  #25 = Utf8               println
  #26 = Utf8               (I)V

类型

描述

CONSTANT_utf8_info

UTF-8 编码的字符串

CONSTANT_Integer_info

整型字面量

CONSTANT_Float_info

浮点型字面量

CONSTANT_Long_info

长整型字面量

CONSTANT_Double_info

双精度浮点型字面量

CONSTANT_Class_info

类或接口的符号引用

CONSTANT_String_info

字符串类型字面量

CONSTANT_Fieldref_info

字段的符号引用

CONSTANT_Methodref_info

类中方法的符号引用

CONSTANT_InterfaceMethodref_info

接口中方法的符号引用

CONSTANT_NameAndType_info

字段或方法的符号引用

CONSTANT_MethodType_info

标志方法类型

CONSTANT_MethodHandle_info

表示方法句柄

CONSTANT_InvokeDynamic_info

表示一个动态方法调用点

构造方法
public Demo1();
    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 1: 0
  • Demo1 中,我们并没有写构造函数。
  • 由此可见,没有定义构造函数时,会有隐式的无参构造函数。
  • descriptor: ()V -> 对于这个东西的理解,是入参为空,返回值为 void
入口函数: main 函数
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: sipush        500
         3: istore_1
         4: bipush        100
         6: istore_2
         7: iload_1
         8: iload_2
         9: idiv
        10: istore_3
        11: bipush        50
        13: istore        4
        15: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
        18: iload_3
        19: iload         4
        21: iadd
        22: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
        25: return
      LineNumberTable:
        line 3: 0
        line 4: 4
        line 5: 7
        line 6: 11
        line 7: 15
        line 8: 25
  • 我们来看看整个程序的执行顺序
  • 0: sipush 500: 将500压入操作数栈

序号

本地变量表

0

args

操作数栈

500

  • 3: istore_1: 将500保存到本地变量表1的位置

序号

本地变量表

0

args

1

500

  • 4: bipush 100: 将100压入操作数栈

序号

本地变量表

0

args

1

500

操作数栈

100

  • 6: istore_2: 将100保存到本地变量表2的位置

序号

本地变量表

0

args

1

500

2

100

  • 7: iload_18: iload_2: 将本地变量表1、2位置的数据压入操作数栈

序号

本地变量表

0

args

1

500

2

100

操作数栈

100

500

  • 9: idiv: 进行除法运算,并且将结果压入操作数栈

序号

本地变量表

0

args

1

500

2

100

操作数栈

5

  • 10: istore_3: 将5(500/100)保存到本地变量表3的位置

序号

本地变量表

0

args

1

500

2

100

3

5

  • 11: bipush 50: 将50压入操作数栈

序号

本地变量表

0

args

1

500

2

100

3

5

操作数栈

50

  • 13: istore 4: 将50保存到本地变量表4的位置

序号

本地变量表

0

args

1

500

2

100

3

5

4

50

  • 15: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;: 将常量池中#2对应的常量压入操作数栈

序号

本地变量表

0

args

1

500

2

100

3

5

4

50

操作数栈

#2

  • 18: iload_3: 将本地变量表中3位置的数据(5)压入操作数栈

序号

本地变量表

0

args

1

500

2

100

3

5

4

50

操作数栈

5

#2

  • 19: iload 4: 将本地变量表中4位置的数据(50)压入操作数栈

序号

本地变量表

0

args

1

500

2

100

3

5

4

50

操作数栈

50

5

#2

  • 21: iadd: 将栈的前两个元素执行加法操作,并将执行结果(50+5=55)压入操作数栈

序号

本地变量表

0

args

1

500

2

100

3

5

4

50

操作数栈

55

#2

  • 22: invokevirtual #3 // Method java/io/PrintStream.println:(I)V: jvm回根据这个方法的描述,创建新栈帧,方法的参数从操作数栈中弹出,压入虚拟机栈中,然后虚拟机栈会开始执行虚拟机栈最上面的栈帧。
  • 25: return: 执行完毕,返回来继续执行main方法,返回,main方法结束。
  • 至此,我们的整个main函数的执行过程便解释完了。

3. 看看整体函数的运行分析吧


1. 加载信息到方法区

java 运行进程 .java运行_java_03

2. JVM创建线程来执行

java 运行进程 .java运行_java 运行进程_04

3. 执行main函数
  • 该部分上面已做分析,在此不再重复。