目录
java入门的第一课,都会讲用java语言开发软件的基本流程,分三步:1、编写源代码,即.java文件;2、将.java文件编译为字节码文件,即.class文件;3、在装有JRE(Java Runtime Environment,java运行时环境)的电脑上运行你编写的软件。那么.class文件里是什么内容呢?JVM虚拟机是怎么工作的呢?让我们一起探究。
一、字节码文件的秘密
.class文件由java源代码文件经过编译器编译得到(编译命令:javac Xxx.java),称为字节码文件,其数据是严格按照格式紧凑排列在.class文件中的二进制流,中间无任何分隔符。用编辑工具打开该文件,我们可以看到,文件开头有一个cafe babe的特殊标志。
![](https://i-blog.csdnimg.cn/blog_migrate/c9c15c064049a058db639bf258482d41.png)
![](https://i-blog.csdnimg.cn/blog_migrate/b23f3f0cde77db775d1a289dcc40895c.png)
肉眼直接看字节码文件肯定看不懂,我们需要一双火眼金睛。我们可以通过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指令码表》,下图是部分截图。
![](https://i-blog.csdnimg.cn/blog_migrate/3ac882f07c59eb7adb038922ae65f985.png)
二、JVM运行时数据区
在讲解JVM具体是如何运行之前,让我们先了解一下JVM运行时数据区有哪些?
![](https://i-blog.csdnimg.cn/blog_migrate/0f5e01d396e8f2c8ed8cd12f0490089d.png)
其中,方法区、堆内存是线程共享区域,即所有线程都能够访问到这两个区域,随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对应状态如下图:
![](https://i-blog.csdnimg.cn/blog_migrate/094890e051b2a8ba79e1d4b86f1b2d23.png)
执行的第一个指令是,0: sipush 500。查《JVM指令码表》,得:
![](https://i-blog.csdnimg.cn/blog_migrate/41f76c1753663024084c69307d9600f2.png)
即,将500压入到线程的操作数栈中。
![](https://i-blog.csdnimg.cn/blog_migrate/33769d264c323e9304d01126adbc007a.png)
同理,3:istore_1,的含义是:将操作数栈顶int类型值存储到本地变量表1的位置。
![](https://i-blog.csdnimg.cn/blog_migrate/5ec00cf6b850503bebd2d3c9337b6fe1.png)
![](https://i-blog.csdnimg.cn/blog_migrate/7fdd6dd214c9bc70e107c25d2717b4d6.png)
下一行,bipush:valuebyte值带符号扩展成int值入栈。
![](https://i-blog.csdnimg.cn/blog_migrate/6270b5294505df91ec66260740b12b24.png)
![](https://i-blog.csdnimg.cn/blog_migrate/4d85abc209729472f964d8ba13744027.png)
![](https://i-blog.csdnimg.cn/blog_migrate/94c4a327227ffd8323d6bbcfe8eceb2e.png)
![](https://i-blog.csdnimg.cn/blog_migrate/ab145a29b13875fcf03e5bad569954de.png)
![](https://i-blog.csdnimg.cn/blog_migrate/b40ce8587fab13cee5ad5232ee302399.png)
![](https://i-blog.csdnimg.cn/blog_migrate/d511d1d7ca36df41250a6257200dbe8b.png)
![](https://i-blog.csdnimg.cn/blog_migrate/45ba3f8d97377cb1e41eeb218ba6642a.png)
![](https://i-blog.csdnimg.cn/blog_migrate/96d5742a4beeffe3b467b043ef1fdad6.png)
![](https://i-blog.csdnimg.cn/blog_migrate/e565f6e792f541ddcf56abf4563cb8f7.png)
![](https://i-blog.csdnimg.cn/blog_migrate/ad8bf26769a6d683109b135e9a2790bd.png)
![](https://i-blog.csdnimg.cn/blog_migrate/507af972f2d477615b5d671f1a557226.png)
![](https://i-blog.csdnimg.cn/blog_migrate/b491b6044d6acae792d4072371ac83f7.png)
![](https://i-blog.csdnimg.cn/blog_migrate/4c8d616fd33844e9c3938dfb77bb5298.png)
![](https://i-blog.csdnimg.cn/blog_migrate/c08e80f341620f8dbd1cc854f008e1ab.png)
四、小结
这一节我们学习了字节码文件、JVM运行时数据区、JVM运行原理及示例,这些内容偏底层,如果有兴趣可以进一步研究。
说在后面
本系列文章为网易云课堂之java高级开发工程师微专业课堂笔记,作者整理只为学习。如果希望系统的学习,请联系网易云课堂的相关工作人员。如有侵权,请联系作者删除。