java运行流程 第二版本

2021年07月07日 第一版 本版本处于不可信阶段
突然想用这样发布一个现阶段对java运行流程的理解,持续学习以及更新文章,让之后的我不要忘了曾经的我有多傻多憨憨。

2022年03月01日 第二版 本版本仍然是处于不可信阶段
最近再次研究JVM,又有所获。主要完善了执行之前的一些细节。让整个流程更加通俗易懂一点(虽然现在也不通俗易懂吧)。
本次更新的地方都标注有 “完善: ”。

好的回归文章:
作为萌新程序员,有没有疑惑当我们使用idea或者eclipse运行一个java程序到底怎么运行呢?
总结其实就两步:
1.javac 命令 编译java文件 生成 .class文件
2.java 命令 解析字节码(.class)文件执行
这就是java基础运行流程,好了文章到此结束了~~!
开个玩笑,主要想稍微详细的介绍一下关于第二个步骤

编译java文件

其实这一步就是生成 target\classes中的class文件。这里主要编译器进行编译,具体就不细讲了。

完善:这一步就是我们点击idea或者eclipe的build。完成这一步我们项目中会出现一个target文件,里面有我们写的java代码编译后的所有的.class文件

解析字节码文件

解析字节码文件其实核心就是 JVM。

完善: .class文件其实就是JVM能识别的字节码文件,会按照 字节码指令手册的规则 执行字节码文件。

类加载

负责加载 字节码文件,如果调用系统类(String),或者其他的自己写的类 分别会调用不同的类加载器去加载。其实就将字节码文件从硬盘上利用Stream读取到内存中,放在元空间(1.8以前放在方法区永久代)中。

ps:这里牵扯到 根加载器,拓展类加载器,应用程序加载器以及双亲委派的知识。加载到内存中InstanceKlass 类的元信息,也就class在jvm的存储形式。

完善:如果指令用到了未曾加载的类,或者说 第一次实例化某一个类的时候(new ClassA()),我们会进行类加载。会将类中对应的 常量+静态变量+类信息 放在方法区(1.8后称之为 元空间)内。
因为JVM其实是懒加载 所以说只有当类被首次运行到才会加载.如果一个方法内有一个类C,但是这个方法从启动到现在未被调用,那么类C信息是没有被加载到方法区中。

执行

public class test{
	int a = 1public int add(int a){
		return ++a; 
	}
	public static void main(String[] args){
		String s = "你好/hello";
        s.split("/");
		int a = 2;
		a= add(a);
	}
}

加载所需要的类之后,就开始执行了。
关于虚拟机栈,每调用一个方法会产生一个栈帧。
栈帧结构

  • 局部变量表

存储方法内的 变量
例如:int a,String s中的 a,s保存在局部变量表中
在这里插入图片描述

  • 操作数栈

存储操作数的栈 字节码指令 iconst istore ldc astore等操作
方法内 int a = 2 = 这个操作iconst_2,istore_1的操作记录a的值复制为2并保存到局部变量表中

  • 动态链接
    Klass类元信息在方法区 一个Klass是一个InstanceKlass实例 类所有的方法都在vtable ,动态链接就是拿到table中具体哪个方法的地址。

在这里插入图片描述
在这里插入图片描述
调用的别的类的方法,通过动态链接拿到方法的地址放在常量池中。

一个线程会创建一个虚拟机栈结构,调用一个方法产生一个栈帧。故执行main方法会 创建一个栈并且栈中创建一个栈帧。

很显然该类应该是创建一个虚拟机栈结构,那么猜猜执行过程中会生成多少个栈帧呢?

公布答案:
在这里插入图片描述
首先会执行init 结束,然后执行main,之后调用add,结束add继续执行main,结束main结束程序。

下面具体讲讲main方法执行的过程。

  • main 方法的字节码
0 iconst_2
 1 istore_1
 2 ldc #2 <你好/hello>
 4 astore_2
 5 aload_2
 6 ldc #3 </>
 8 invokevirtual #4 <java/lang/String.split>
11 pop
12 iload_1
13 invokestatic #5 <com/demo/fileupload/test.add>
16 istore_1
17 return
  • add方法的字节码
0 iinc 0 by 1
3 iload_0
4 ireturn

JVM运行main方法

  1. 创建运行main方法需要的栈帧
  2. 将main方法的操作数栈 赋值给 线程的属性 :操作数栈
  3. 将main方法的局部变量表 赋值给 线程的属性 :局部变量表
  4. 创建add方法的栈帧
  5. 保存main方法的下一个字节码指令到程序计数器
  6. 线程的局部表的开始指针(main的)保存到add方法栈帧中
  7. 线程的操作数栈的开始指针(main的)保存到add方法栈帧中
  8. 将add方法的局部表指针以及操作数栈的指针 分别赋值给 线程的局部表的 开始指针以及线程的操作数栈的开始指针
  9. 执行add字节码指令并返回,销毁栈帧
  10. 从程序计数器中读取main方法该执行到哪一条指令,继续执行到结束

以上的过程中若调用到本地方法,会去本地方法栈调用native方法。调用其它类也会去类的元信息去找所需类。例如上述代码用到 String,那么类加载过程中就会将String.class加载到元空间中,我们在虚拟机栈操作指令时候需要用到String类的方法,就会去元空间找到类的元信息。

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值