目录

一、字节码文件的秘密

二、JVM运行时数据区

三、JVM工作原理

四、小结

说在后面


java入门的第一课,都会讲用java语言开发软件的基本流程,分三步:1、编写源代码,即.java文件;2、将.java文件编译为字节码文件,即.class文件;3、在装有JRE(Java Runtime Environment,java运行时环境)的电脑上运行你编写的软件。那么.class文件里是什么内容呢?JVM虚拟机是怎么工作的呢?让我们一起探究。

一、字节码文件的秘密

.class文件由java源代码文件经过编译器编译得到(编译命令:javac Xxx.java),称为字节码文件,其数据是严格按照格式紧凑排列在.class文件中的二进制流,中间无任何分隔符。用编辑工具打开该文件,我们可以看到,文件开头有一个cafe babe的特殊标志。


java 到处work java怎么工作_java

图1-1 java

源代码


 

 


java 到处work java怎么工作_字节码_02

图1-2 字节码文件(16 进制)

 

 

肉眼直接看字节码文件肯定看不懂,我们需要一双火眼金睛。我们可以通过javap命令我们可以将字节码文件转换为指令码文件。如,javap –v Demo1.class>Demo1.txt,这个命令可以将Demo1.class字节码文件转换为Demo1.txt指令码文件,如下:


Classfile /F:/Demo1.class

  Last modified 2019-3-5; size 414 bytes

  MD5 checksum ae6fa820973681b35609c75631cb255b

  Compiled from "Demo1.java"

public class Demo1

  minor version: 0//次版本号

  major version: 52//主版本号(JDK 5/6/7/8分别对应49/50/51/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();//构造函数start

    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//构造函数end



  public static void main(java.lang.String[]);//main方法start

    descriptor: ([Ljava/lang/String;)V

    flags: ACC_PUBLIC, ACC_STATIC//访问控制

Code:

//方法对应栈帧中操作数栈的深度stack=3;本地变量数量locals=5;参数数量args_size=1

      stack=3, locals=5, args_size=1

         0: sipush        500//指令码start//详细请查阅《JVM指令码表》

         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//指令码end//main方法end

      LineNumberTable:

        line 3: 0

        line 4: 4

        line 5: 7

        line 6: 11

        line 7: 15

        line 8: 25

}

SourceFile: "Demo1.java"

(以上“//注释部分”为作者添加的注释内容)。



通过指令码文件,我们可以发现,字节码文件的内容主要包括:版本、访问标志、常量池、当前类、超级类、接口、字段、方法、属性等几部分内容。

将大象放进冰箱分:1、打开冰箱门;2、将大象放进去;3、关上冰箱门。类似的,字节码文件就像是冰箱的操作说明书,JVM加载完字节码文件之后就可以按部就班的运行了。具体某个指令对应的操作内容可以查阅《JVM指令码表》,下图是部分截图。


java 到处work java怎么工作_JVM_03

图1-3 指令码表

二、JVM运行时数据区

在讲解JVM具体是如何运行之前,让我们先了解一下JVM运行时数据区有哪些?


java 到处work java怎么工作_java_04

图1-4 JVM 运行时数据区

其中,方法区、堆内存是线程共享区域,即所有线程都能够访问到这两个区域,随JVM虚拟机或者GC而创建和销毁。虚拟机栈、本地方法栈、程序计数器,属于线程独占区域,即JVM会为每个线程划分一个独立的空间,随着线程生命周期而创建和销毁。

方法区:主要存储加载的类信息、常量、静态变量、编译后的代码等数据;

堆内存:JVM启动时创建,用于存放对象的实例,如果堆内存不够了会报OutOfMemroyError。堆内存还可以细分为老年代、新生代,新生代又分为Eden、From Survivor或S1、To Survivor或S2,三个区域。

虚拟机栈:由多个栈帧(Stack Frame)组成,一个方法对应一个栈帧,是每个线程私有的空间。栈帧包括局部变量表、操作数栈、动态链接、方法返回地址、附加信息等内容。虚拟机栈默认最大是1M,超出后报StackOverflowError。

本地方法栈:为运行Native本地方法准备的内存区域。

程序计数器(Program Counter Register):记录当前线程执行字节码的位置(字节码指令地址),如果执行Native方法,则其值为空。

三、JVM工作原理

知道了JVM运行时数据区的划分,接下来我们看看JVM是怎么工作的。

首先,JVM会加载指定的字节码文件到方法区中;然后,JVM会创建线程来执行字节码文件中的指令;执行完或者有异常情况,退出程序。

我们以Demo1.txt指令码文件为例:

JVM将字节码文件加载到方法区,然后从main方法开始执行。


public static void main(java.lang.String[]);//main方法start

    descriptor: ([Ljava/lang/String;)V

    flags: ACC_PUBLIC, ACC_STATIC//访问控制

Code:

//方法对应栈帧中操作数栈的深度stack=3;本地变量数量locals=5;参数数量args_size=1

      stack=3, locals=5, args_size=1

         0: sipush        500//指令码start

         3: istore_1

         4: bipush        100

以上是main方法的部分指令码,JVM对应状态如下图:


java 到处work java怎么工作_字节码_05

图1-5 程序入口main 方法

执行的第一个指令是,0: sipush 500。查《JVM指令码表》,得:


java 到处work java怎么工作_java 到处work_06

1-6 查《 JVM 指令码表》

即,将500压入到线程的操作数栈中。


java 到处work java怎么工作_JVM_07

图1-7 执行sipush 指令

同理,3:istore_1,的含义是:将操作数栈顶int类型值存储到本地变量表1的位置。


java 到处work java怎么工作_java_08

图1-8 查istore 指令含义

java 到处work java怎么工作_字节码_09

图1-9 执行istore_1 指令

下一行,bipush:valuebyte值带符号扩展成int值入栈。


java 到处work java怎么工作_JVM_10

图1-10 执行bipush 100 指令

java 到处work java怎么工作_JVM_11

图1-11 执行istore_2 指令

java 到处work java怎么工作_JVM_12

图1-12 执行iload_1 指令

java 到处work java怎么工作_字节码_13

图1-13 执行iload_2 指令

java 到处work java怎么工作_java_14

图1-14 执行idiv 指令

java 到处work java怎么工作_字节码_15

图1-15 执行istore_3 指令

java 到处work java怎么工作_字节码_16

图1-16 执行bipush 50 指令

java 到处work java怎么工作_JVM_17

图1-17 执行istore 4 指令

java 到处work java怎么工作_JVM_18

图1-18 执行getstatic #2 指令

java 到处work java怎么工作_java 到处work_19

图1-19 执行iload_3 指令

java 到处work java怎么工作_字节码_20

图1-20 执行iload 4 指令

 


java 到处work java怎么工作_JVM_21

图1-21 执行iadd 指令

java 到处work java怎么工作_JVM_22

图1-22 执行invokevirtual #3 指令(调用System.out.println() 方法)

java 到处work java怎么工作_java_23

图1-23 程序执行结束

四、小结

这一节我们学习了字节码文件、JVM运行时数据区、JVM运行原理及示例,这些内容偏底层,如果有兴趣可以进一步研究。