JVM的运行数据区和过程

从最高的抽象看,JVM获取Java的字节码,从而输出一定的值或者改变自身的状态。

image-20230802232128642

所以接下来有一些问题,如

  • java bytecode 的格式是什么
  • JVM是如何获得Java bytecode的
  • JVM是怎么产生值和改变状态的

JVM如何改变状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Text{
final static int t = 1;
int y;
Text(int _y){
y = _y;
}
public int doubleY(){
return 2*y;
}
}
class Main{
public static void main(String[] args) {
int k = 1;
Text t = new Text(2);
Text t1 = new Text(3);
int l = t.doubleY();
int j = t1.doubleY();
}
}

JVM如何改变状态,换句话说,是指JVM虚拟机在执行JAVA字节码时,环境怎么变化。我说下JVM在执行上面代码的时候,所产生的状态变化,带大家了解JVM的基本数据空间,和JVM的运行时数据空间的变化。

如何开始

我们知道,Java代码是由一个个类构成,通过创建对象,运行对象的方法,从而改变JVM的状态。而每一个类都会生成一个CLASS文件。你可以做一个测试。

创建一个HelloWorld.java文件,里面代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//Declaring HelloWorld class.    
public class HelloWorld{
//creating main() method of the HelloWorld class
public static void main(String args[]){
//Printing Hello World
Text t = new Text();
System.out.println("This is HelloWorld! example");
}
}
abstract class hh{}
class Text{
public int h(){
return 1;
}
}

使用”javac HelloWorld.java”编译文件

![](E:\Z\blog\source\picture\Screenshot 2023-08-05 223726.png)

将会看到

![](E:\Z\blog\source\picture\Screenshot 2023-08-05 223822.png)

在这个文档里,发现JVM开始时会创建一个initial类,通过这个类,初始化这个类,通过这个类,调用”public main”方法。

![](E:\Z\blog\source\picture\Screenshot 2023-08-05 224810.png)

初始化环境

当JVM运行字节码文件时时,首先要初始化环境(加载类),为之后的运行代码提供条件,有五步骤

  • 加载:得到数据
  • 验证:判断数据是否安全,能不能执行,有没有Bug
  • 开辟空间:在方法区里,开辟一定的空间
  • 引用转换:把符号引用变成直接引用
  • 填空间:对第三部开辟的空间赋值

对于上面的程序,在JVM初始化时,会将”Text” 和”Main”的类信息存到方法区里,初始化单个线程对应的栈,本地方法栈,程序计数器,初始化堆。

运行代码

当初始化完成,JVM在方法区中找到”public static void main(String[] args)”方法后,运行方法里的代码,代码运行完成,JVM运行完成。

3

下面是上面程序在”public static void main(String[] args)”方法里的代码,我们看看JVM在执行这些代码时,引起的数据区域变化。

1
2
3
4
5
int k = 1;
Text t = new Text(2);
Text t1 = new Text(3);
int l = t.doubleY();
int j = t1.doubleY();

方法开始运行时,会在数据区域的栈顶新建一个栈帧,可以理解成一个大型的变量(环境)。方法运行时,通过改变环境来实现一定功能。

普通数据类型
1
int k = 1;

环境变化如图

4

创建对象
1
2
Text t = new Text(2);
Text t1 = new Text(3);

当创建对象时,JVM首先在方法区(存储类信息的地方)看有没有这个类,找到类后在堆区创建一个没有初始化的对象区,在调用构造器方法后,对象区初始化,之后将对象的引用赋给变量。

注意:对象中的方法是每个对象(属于一个类)共有的,如果每有一个对象就给创建它所有的方法,我认为太浪费,JVM官方不会这样弄。

调用方法
1
2
int l = t.doubleY();
int j = t1.doubleY();

当调用方法时,Stack会新建一个栈帧(环境),在这个环境里面执行方法体中的代码,当代码结束时,丢弃环境(POP),将返回值赋值给最初调用的地方。

JVM的调试工具

对于一个解释器JVM,调试工具必不可少

jps

这是一个监测虚拟机状态的工具,为了使用它,首先我们定义一些进程。

1
2
3
4
5
class Main{
public static void main(String[] args) {
while (true){}
}
}
1
2
3
4
5
public class SwingDemo {
public static void main(String[] args) {
while (true){}
}
}

jps -l

这个命令是输出运行进程对应的LVMID(进程ID)和主类名,如果是JAR包,输出路径

当运行上面两个类时,之后运行”jps -l”

![](E:\Z\blog\source\picture\Screenshot 2023-08-03 160417.png)

jps -q

输出LVMID,省略主类名称

![](E:\Z\blog\source\picture\Screenshot 2023-08-03 161118.png)

jps -m

输出调用进程时,传递的给方法”main”的参数

![](E:\Z\blog\source\picture\Screenshot 2023-08-03 161303.png)

jps -v

输出JVM启动时的参数,比如堆的大小等

![](E:\Z\blog\source\picture\Screenshot 2023-08-03 161750.png)

CLASS文件格式

还记的这个图吗

image-20230802232128642

现在我们来讲解那个云朵部分,云朵里面包含有八个信息部分

  • 类信息:类的签名和版本号
  • 常量信息:常量指广义的常量,除开final修饰的常量,还有类名,方法名等
  • 进入权限:表示类的权限,public,还是,private
  • 继承信息:继承的父类
  • 接口信息:实现的接口
  • 方法的信息:方法名和方法的权限
  • 属性信息:存些属性值,如方法的方法体部分