java计算器程序运行结果分析怎么写

2025年03月10日 阅读 (55)

本文是JVM入门教程的第一篇。在这篇博客中,将介绍JVM的基本结构和相关的概念,并通过一个简单的示例,演示java程序的运行过程。

java计算器程序运行结果分析怎么写(1)

如上图所示,java代码经过编译之后,产生了class文件。java程序运行时,JVM会为class文件分配一个内存空间,存储它运行时的信息,即JVM运行数据区。JVM运行数据区按照线程是否独占或共享,进一步划分为若干个不同的数据区域。其中,线程共享部分包括方法区和堆内存,线程独占部分包括虚拟机栈、本地方法栈、程序计数器。

简而言之,方法区主要用来存储class文件的一些数据,比如虚拟机加载的类信息、常量、静态变量、即时编译器编译过后的代码等数据。它是虚拟机规范中的一个逻辑区划。具体实现根据不同的虚拟而不同。比如HotSpot在java7中方法区放在永久代,java8中方法区放在元数据空间并且通过GC机制对这个区域进行管理。

类加载之后,我们就可能需要用这些类去创建一些对象。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。其中,堆内存还可以细分为老年代、新生代。

程序计数器是一块较小的内存空间,可以看作当前线程所执行的字节码的行号指示器,记录的是当前线程所执行的位置。CPU同一时间,只会执行一条线程中的指令,而线程切换后,通过线程独立的线程计数器所指向的字节码的行号,就能回到上次最后指向的位置,并且继续执行剩下的字节码。

每个线程都在这个这个空间有一个私有的空间。线程栈由多个栈帧组成。一个线程会执行一个或多个方法,一个方法对应着一个栈帧。栈帧的内容包含:局部变量表,操作数栈、动态连接、方法返回地址、附加信息等。栈内存默认最大是1M,超出则抛出StackOverflowError。

和虚拟机栈功能类似,虚拟机栈是为虚拟机执行java方法而准备的,本地方法栈是为虚拟机使用Native本地方法而准备的。他和虚拟机栈的实现一样,超出大小也会抛出StackOverflowError。具体实现由不同的虚拟机厂商决定。

  • 系统: win10
  • JDK:jdk1.8
  • 16进制文件查看工具:winhex

在这里,我们定义了一个简单的java类Demo1,在类的main方法中,定义了简单的计算逻辑,我们可以很快地看出,打印出来的结果是55。但这不是重点,我们主要是用来展示java程序是如何在JVM中运行的。

publicclassDemo1{publicstaticvoidmain(String[] args){intx =500;inty =100;inta = x / y;intb =50;        System.out.println;    }}复制代码

我们在命令行中定位到这个java文件所在,进行编译。

javacDemo1.java复制代码
java计算器程序运行结果分析怎么写(2)

class文件包含java程序代码执行的字节码,数据严格按照格式紧凑排列在class文件的二进制流,中间无任何分隔符。文件开头有一个0xcafebabe(16进制)特殊的标志。

java计算器程序运行结果分析怎么写(3)

为了更好地阅读class文件的内容,我们可以使用javap命令,解析这个文件,并将文件内容写到Demo1.txt中。

javap -v Demo1.classDemo1.txt复制代码

在当前java文件所在的目录下,就会生成一个Demo1.txt文件,我们打开这个文件,文件的内容如下:

Classfile/C:/Java高级工程师项目/jvm_demo/Demo1.classLastmodified2020-8-22;size414bytesMD5checksumae6fa820973681b35609c75631cb255bCompiledfrom"Demo1.java"publicclassDemo1minor version:0major version:52flags:ACC_PUBLIC,ACC_SUPERConstant pool:5.#14         // java/lang/Object."init":()V15.#16        // java/lang/System.out:Ljava/io/PrintStream;17.#18        // java/io/PrintStream.println:(I)V19            // Demo120            // 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.java6:#7          // "init":()V21            // java/lang/System22:#23        // out:Ljava/io/PrintStream;24            // java/io/PrintStream25:#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{publicDemo1();descriptor:()Vflags:ACC_PUBLICCode:stack=1,locals=1,args_size=10:aload_01:invokespecial#1                  // Method java/lang/Object."init":()V4:returnLineNumberTable:line 1:0publicstaticvoidmain(java.lang.String[]);descriptor:([Ljava/lang/String;)Vflags:ACC_PUBLIC,ACC_STATICCode:stack=3,locals=5,args_size=10:sipush5003:istore_14:bipush1006:istore_27:iload_18:iload_29:idiv10:istore_311:bipush5013:istore415:getstatic#2                  // Field java/lang/System.out:Ljava/io/PrintStream;18:iload_319:iload421:iadd22:invokevirtual#3                  // Method java/io/PrintStream.println:(I)V25:returnLineNumberTable:line 3:0line 4:4line 5:7line 6:11line 7:15line 8:25}SourceFile:"Demo1.java"复制代码

我们先来看Classfile这部分内容:

Classfile /C:/Java高级工程师项目/jvm_demo/Demo1.classLast modified2020-8-22; size414bytes  MD5 checksum ae6fa820973681b35609c75631cb255b  Compiledfrom"Demo1.java"复制代码

它描述了class文件所在的路径,最后的更新时间,所占字节大小,md5的校验码,以及指明了从哪个java文件编译过来。

publicclass Demo1minorversion表示次版本号,: 0majorversion: 52flags:ACC_PUBLIC, ACC_SUPER复制代码

在这里,major version表示主版本号,minor version表示此版本号,flag表示访问标志,访问标志的含义可以参见下表。

java计算器程序运行结果分析怎么写(4)
Constant pool:#1 = Methodref14         // java/lang/Object."init":()V#2 = Fieldref16        // java/lang/System.out:Ljava/io/PrintStream;#3 = Methodref18        // 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 = NameAndType7          // "init":()V#15 = Class#21            // java/lang/System#16 = NameAndType23        // out:Ljava/io/PrintStream;#17 = Class#24            // java/io/PrintStream#18 = NameAndType26        // 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复制代码

这里存放的是类信息包含的静态常量,编译之后就能确认。这里使用的标识所对应的含义,参见下表。

java计算器程序运行结果分析怎么写(5)

我们对照着Demo1.java文件和Demo1.txt文件,参照上面的表格,可以看到常量池中存储着哪些常量: 比如,java默认都继承了Object类,所以这里会引用Object类和相关无参构造方法的常量,同时,我们在代码中使用了System.out.println()方法,所以也引用了System.out.println()方法相关的类、方法、字段等常量。然后,我们系统的编码是UTF-8,常量池中也会记录相关引用的UTF-8编码的字符串常量。(9中的LineNumberTable用来表示java源代码的行号和字节码指令的对应关系,都是一些默认的常量,在这里我们了解即可)

publicDemo1();descriptor:()Vflags:ACC_PUBLICCode:stack=1, locals=1, args_size=10:aload_01:invokespecial #1                  // Method java/lang/Object."init":()V4:returnLineNumberTable:line1: 0复制代码

这部分内容,描述的是class文件的构造方法信息。Demo1这个示例中,我们并没有写构造函数,由此可见,没有定义构造函数时,会有隐式的无参构造函数。flags中的ACC_PUBLIC表示是public类型,可以参考上面的class内容-基本信息的表格。code表示方法表。“stack=1, locals=1, args_size=1”分别表示,方法对应栈帧中操作数栈的深度是1,本地变量的个数是1,参数个数是1。本地变量和参数的个数都为1,是因为构造方法本身默认包含了this参数。

我们接着往下看。“0: aload_0” 表示从局部变量0中装载引用类型值入栈,在这里的局部变量0就是this变量,前面的数字,是偏移量。“1: invokespecial 1对应的方法,即Object的无参构造方法。“4: return”则表示void函数返回。"LineNumberTable"表示源代码与字节码指令的对应关系。“line 1: 0”说明源代码的第一行指向偏移量为0的字节码指令,在这里即是“0: aload_0”。

看到这里,可能有些同学会有一些困惑,偏移量到底是什么,是怎么计算来的。下面我来简单的说一下。

  1. 偏移量表示相对入口地址偏移,单位是字节。比如偏移量为1,则说明偏移了一个字节的地址。
  2. 下一个字节码的偏移量=当前偏移量+当前字节码的长度。
  3. 字节码长度=操作码所占字节个数+操作数所占字节个数

郑重声明:玄微运势的内容来自于对中国传统文化的解读,对于未来的预测仅供参考。